Merge branch 'master' into add-send-only-aliases

This commit is contained in:
Noah Overcash 2025-03-31 21:08:12 -04:00
commit bed51886f4
No known key found for this signature in database
107 changed files with 2727 additions and 3301 deletions

View File

@ -1,26 +0,0 @@
{
"files": [
"CONTRIBUTORS.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "matrixes",
"name": "matrixes",
"avatar_url": "https://avatars.githubusercontent.com/u/46491408?v=4",
"profile": "https://github.com/matrixes",
"contributions": [
"blog"
]
}
],
"contributorsPerLine": 7,
"badgeTemplate": "",
"projectName": "docker-mailserver",
"projectOwner": "docker-mailserver",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": false,
"commitConvention": "none"
}

View File

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

1
.gitattributes vendored
View File

@ -28,7 +28,6 @@ Makefile
*.yaml text *.yaml text
## PROJECT ## PROJECT
.all-contributorsrc text export-ignore
.editorconfig text export-ignore .editorconfig text export-ignore
.gitattributes text export-ignore .gitattributes text export-ignore
.gitignore text export-ignore .gitignore text export-ignore

View File

@ -67,10 +67,3 @@ 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

@ -4,6 +4,8 @@ title: 'feature request: '
labels: labels:
- kind/new feature - kind/new feature
- meta/needs triage - meta/needs triage
projects:
- DMS Core Backlog
body: body:
- type: markdown - type: markdown

View File

@ -4,8 +4,6 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "weekly"
reviewers:
- "docker-mailserver/maintainers"
labels: labels:
- "area/ci" - "area/ci"
- "kind/update" - "kind/update"
@ -15,8 +13,6 @@ updates:
directory: / directory: /
schedule: schedule:
interval: "weekly" interval: "weekly"
reviewers:
- "docker-mailserver/maintainers"
labels: labels:
- "area/ci" - "area/ci"
- "kind/update" - "kind/update"

View File

@ -1,33 +0,0 @@
name: 'Update Contributors List'
on:
workflow_dispatch:
schedule:
- cron: 0 4 * * 0
permissions:
contents: write
pull-requests: write
statuses: write
jobs:
add-contributors:
name: 'Add Contributors'
runs-on: ubuntu-22.04
steps:
- name: 'Checkout'
uses: actions/checkout@v4
- name: 'Update CONTRIBUTORS.md'
uses: akhilmhdh/contributors-readme-action@v2.3.10
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
readme_path: CONTRIBUTORS.md
collaborators: all
use_username: true
commit_message: 'docs: updated `CONTRIBUTORS.md`'
committer_username: github-actions[bot]
committer_email: 41898282+github-actions[bot]@users.noreply.github.com
pr_title_on_protected: 'docs: update `CONTRIBUTORS.md`'
auto_detect_branch_protection: true

View File

@ -1,119 +1,166 @@
name: 'Documentation (run)' name: 'Documentation (Deploy)'
on: on:
# This workflow runs off the primary branch which provides access to the `secrets` context:
workflow_run: workflow_run:
workflows: ['Documentation (PR)'] workflows: ['Documentation (PR)']
types: types:
- completed - completed
# Note: If limiting concurrency is required for this workflow: permissions:
# 1. Add an additional job prior to `preview` to get the PR number make it an output. # Required by `actions/download-artifact`:
# 2. Assign that new job as a `needs` dependency for the `preview` job. actions: read
# It is still required for `preview` job to download the artifact so that it can access the preview build files. # Required by `set-pr-context`:
contents: read
# Required by `marocchino/sticky-pull-request-comment` (write) + `set-pr-context` (read):
pull-requests: write
# Required by `myrotvorets/set-commit-status-action`:
statuses: write
# This workflow runs off the primary branch and has access to secrets as expected.
jobs: jobs:
preview: # NOTE: This is handled as pre-requisite job to minimize the noise from acquiring these two outputs needed for `deploy-preview` ENV:
name: 'Deploy Preview' pr-context:
runs-on: ubuntu-22.04 name: 'Acquire PR Context'
if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} 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: 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:
# Restore workflow context # name: 'Deploy Preview'
# ======================== # runs-on: ubuntu-24.04
needs: [pr-context]
# Retrieve the artifact uploaded from `docs-preview-prepare.yml` workflow run that triggered this deployment env:
- name: 'Download build artifact' # 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:
- 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 }}
# 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 }} github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }} run-id: ${{ github.event.workflow_run.id }}
- name: 'Extract build artifact'
run: tar -xf artifact.tar.zst
- name: 'Restore preserved ENV'
run: cat pr.env >> "${GITHUB_ENV}"
# ==================== # # ==================== #
# Deploy preview build # # Deploy preview build #
# ==================== # # ==================== #
# Manage workflow deployment status. `enable-commit-status` from `nwtgck/actions-netlify` would handle this, # Manage workflow deployment status (Part 1/2):
# but presently does not work correctly via split workflow. It is useful in a split workflow as the 1st stage # NOTE:
# no longer indicates if the entire workflow/deployment was successful. # - `workflow_run` trigger does not appear on the PR/commit checks status, only the initial prepare workflow triggered.
- name: 'Commit Status: Set Workflow Status as Pending' # 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 uses: myrotvorets/set-commit-status-action@v2.0.1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
status: pending status: pending
# Should match `env.PR_HEADSHA` when triggered by `pull_request` event workflow, sha: ${{ env.PR_HEADSHA }}
# Avoids failure of ENV being unavailable if job fails early:
sha: ${{ github.event.workflow_run.head_sha }}
context: 'Deploy Preview (pull_request => workflow_run)' 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 id: preview-netlify
timeout-minutes: 1 timeout-minutes: 1
env: env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} # Fail the job when the required Netlify credentials are missing from ENV:
# Fail the job early if credentials are missing / invalid:
fails-without-credentials: true fails-without-credentials: true
# Sets/creates the Netlify deploy URL prefix. # Set/create the Netlify deploy URL prefix:
# Uses the PR number for uniqueness: alias: ${{ env.PREVIEW_SITE_PREFIX }}
alias: ${{ env.NETLIFY_SITE_PREFIX }}
# Only publish the contents of the build output: # Only publish the contents of the build output:
publish-dir: ${{ env.BUILD_DIR }} publish-dir: ${{ env.BUILD_DIR }}
# Custom message for the deploy log on Netlify: # Custom message for the deploy log on Netlify:
deploy-message: '${{ env.PR_TITLE }} (PR #${{ env.PR_NUMBER }} @ commit: ${{ env.PR_HEADSHA }})' deploy-message: 'Preview Build (PR #${{ env.PR_NUMBER }} @ commit: ${{ env.PR_HEADSHA }}'
# Note: Split workflow incorrectly references latest primary branch commit for deployment.
# Assign to non-default Deployment Environment for better management:
github-deployment-environment: documentation-previews
github-deployment-description: 'Preview deploy for documentation PRs'
# Note - PR context used by this action is incorrect. These features are broken with split workflow:
# https://github.com/nwtgck/actions-netlify/issues/545
# Disable unwanted action defaults: # Disable unwanted action defaults:
# Disable adding deploy comment on pre-merge commit (Github creates this for PR diff): # This input does not fallback to the GITHUB_TOKEN taken from context, nor log that it will skip extra features of the action when this input is not set:
# https://github.com/nwtgck/actions-netlify/issues/1219
# github-token: ${{ secrets.GITHUB_TOKEN }}
# NOTE: These features won't work correctly when the triggered workflow is not run from the PR branch due to assumed `pull_request` context:
# https://github.com/nwtgck/actions-netlify/issues/545
# Disable adding a comment to the commit belonging to context `github.sha` about the successful deployment (redundant and often wrong commit):
enable-commit-comment: false enable-commit-comment: false
# Disable adding a "Netlify - Netlify deployment" check status: # Disable adding a "Netlify - Netlify deployment" PR check status (workflow job status is sufficient):
enable-commit-status: false enable-commit-status: false
# Disable. We provide a custom PR comment in the next action: # Disable adding a comment about successful deployment status to the PR.
# Prefer `marocchino/sticky-pull-request-comment` instead (more flexible and allows custom message):
enable-pull-request-comment: false enable-pull-request-comment: false
# Opt-out of deployment feature:
# NOTE:
# - When affected by `nwtgck/actions-netlify/issues/545`, the deployments published reference the wrong commit and thus information.
# - While the feature creates or assigns a deployment to associate the build with, it is unrelated to the related environments feature (secrets/vars):
# https://github.com/nwtgck/actions-netlify/issues/538#issuecomment-833983970
# https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/viewing-deployment-history
# https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment
enable-github-deployment: false
# Assign to non-default Deployment Environment for better management:
# github-deployment-environment: documentation-previews
# github-deployment-description: 'Preview deploy for documentation PRs'
# If a `netlify.toml` config is ever needed, enable this: # If a `netlify.toml` config is ever needed, enable this:
# netlify-config-path: ./docs/netlify.toml # netlify-config-path: ./docs/netlify.toml
# If ever switching from Github Pages, enable this conditionally (false by default): # If ever switching from Github Pages, enable this only when not deploying a preview build (false by default):
# production-deploy: false # production-deploy: false
- name: 'Comment on PR: Add/Update deployment status' - name: 'Comment on PR with preview link'
uses: marocchino/sticky-pull-request-comment@v2 uses: marocchino/sticky-pull-request-comment@v2
with: with:
number: ${{ env.PR_NUMBER }} number: ${{ env.PR_NUMBER }}
header: preview-comment header: preview-comment
recreate: true recreate: true
message: | message: |
[Documentation preview for this PR](${{ steps.preview.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 }}
- name: 'Commit Status: Update deployment status' # Manage workflow deployment status (Part 2/2):
- name: 'Commit Status (2/2) - Update deployment status'
uses: myrotvorets/set-commit-status-action@v2.0.1 uses: myrotvorets/set-commit-status-action@v2.0.1
# Always run this step regardless of job failing early: # Always run this step regardless of the job failing early:
if: ${{ always() }} if: ${{ always() }}
# Custom status descriptions:
env: env:
DEPLOY_SUCCESS: Successfully deployed preview. DEPLOY_SUCCESS: Successfully deployed preview.
DEPLOY_FAILURE: Failed to deploy preview. DEPLOY_FAILURE: Failed to deploy preview.
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status == 'success' && 'success' || 'failure' }} status: ${{ job.status == 'success' && 'success' || 'failure' }}
sha: ${{ github.event.workflow_run.head_sha }} sha: ${{ env.PR_HEADSHA }}
context: 'Deploy Preview (pull_request => workflow_run)' context: 'Deploy Preview (pull_request => workflow_run)'
description: ${{ job.status == 'success' && env.DEPLOY_SUCCESS || env.DEPLOY_FAILURE }} description: ${{ job.status == 'success' && env.DEPLOY_SUCCESS || env.DEPLOY_FAILURE }}

View File

@ -7,74 +7,68 @@ on:
- '.github/workflows/scripts/docs/build-docs.sh' - '.github/workflows/scripts/docs/build-docs.sh'
- '.github/workflows/docs-preview-prepare.yml' - '.github/workflows/docs-preview-prepare.yml'
# If the workflow for a PR is triggered multiple times, previous existing runs will be canceled. # If this workflow is triggered while already running for the PR, cancel any earlier running instances:
# eg: Applying multiple suggestions from a review directly via the Github UI. # Instances of the 2nd phase of this workflow (via `workflow_run`) lack any concurrency limits due to added complexity.
# Instances of the 2nd phase of this workflow (via `workflow_run`) presently lack concurrency limits due to added complexity.
concurrency: concurrency:
group: deploypreview-pullrequest-${{ github.event.pull_request.number }} group: deploypreview-pullrequest-${{ github.event.pull_request.number }}
cancel-in-progress: true cancel-in-progress: true
env:
# Build output directory (created by the mkdocs-material container, keep this in sync with `build-docs.sh`):
BUILD_DIR: docs/site/
# These two are only needed to construct `PREVIEW_URL`:
PREVIEW_SITE_NAME: dms-doc-previews
PREVIEW_SITE_PREFIX: pullrequest-${{ github.event.pull_request.number }}
# `pull_request` workflow is unreliable alone: Non-collaborator contributions lack access to secrets for security reasons. # `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. # 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/ # See: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
permissions: permissions:
# Required by `actions/checkout` for git checkout:
contents: read contents: read
jobs: jobs:
prepare-preview: prepare-preview:
name: 'Build Preview' name: 'Build Preview'
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04
env:
BUILD_DIR: docs/site
NETLIFY_SITE_PREFIX: pullrequest-${{ github.event.pull_request.number }}
NETLIFY_SITE_NAME: dms-doc-previews
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: 'Build with mkdocs-material via Docker' # ================== #
working-directory: docs # Build docs preview #
env: # ================== #
PREVIEW_URL: 'https://${NETLIFY_SITE_PREFIX}--${NETLIFY_SITE_NAME}.netlify.app/'
NETLIFY_BRANDING: '<a href="https://www.netlify.com/"><img alt="Deploys by Netlify" src="https://www.netlify.com/img/global/badges/netlify-color-accent.svg" style="float: right;"></a>'
run: |
# Adjust mkdocs.yml for preview build
sed -i "s|^site_url:.*|site_url: '${PREVIEW_URL}'|" mkdocs.yml
# Insert sponsor branding into page content (Provider OSS plan requirement): - name: 'Build with mkdocs-material via Docker'
# Upstream does not provide a nicer maintainable way to do this.. working-directory: docs/
# Prepends HTML to copyright text and then aligns to the right side. env:
PREVIEW_URL: 'https://${{ env.PREVIEW_SITE_PREFIX }}--${{ env.PREVIEW_SITE_NAME }}.netlify.app/'
run: |
# Adjust `mkdocs.yml` for the preview build requirements:
# - Replace production `site_url` with the preview URL (only affects the canonical link: https://en.wikipedia.org/wiki/Canonical_link_element#HTML)
# - Prepend Netlify logo link to `copyright` content
sed -i "s|^site_url:.*|site_url: '${{ env.PREVIEW_URL }}'|" mkdocs.yml
# Insert branding into page content (Netlify OSS plan requirement):
# - `mkdocs-material` does not provide a better way to do this.
# - Prepends HTML to the copyright text and then aligns the logo to the right-side of the page.
NETLIFY_BRANDING='<a href="https://www.netlify.com/"><img alt="Deploys by Netlify" src="https://www.netlify.com/img/global/badges/netlify-color-accent.svg" style="float: right;"></a>'
sed -i "s|^copyright: '|copyright: '${NETLIFY_BRANDING}|" mkdocs.yml sed -i "s|^copyright: '|copyright: '${NETLIFY_BRANDING}|" mkdocs.yml
# Need to override a CSS media query for parent element to always be full width: # Override a CSS media query for the parent element to always be full width:
echo '.md-footer-copyright { width: 100%; }' >> content/assets/css/customizations.css echo '.md-footer-copyright { width: 100%; }' >> content/assets/css/customizations.css
../.github/workflows/scripts/docs/build-docs.sh # Build and prepare for upload:
echo "::group::Build (stdout)"
bash ../.github/workflows/scripts/docs/build-docs.sh
echo "::endgroup::"
# ============================== # # ============================== #
# Volley over to secure workflow # # Volley over to secure workflow #
# ============================== # # ============================== #
# Minimize risk of upload failure by bundling files to a single compressed archive (tar + zstd). # Archives directory `path` into a ZIP file:
# Bundles build dir and env file into a compressed archive, nested file paths will be preserved.
- name: 'Prepare artifact for transfer'
env:
# As a precaution, reference this value by an interpolated ENV var;
# instead of interpolating user controllable input directly in the shell script..
# https://github.com/docker-mailserver/docker-mailserver/issues/2332#issuecomment-998326798
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
# Save ENV for transfer
{
echo "PR_HEADSHA=${{ github.event.pull_request.head.sha }}"
echo "PR_NUMBER=${{ github.event.pull_request.number }}"
echo "PR_TITLE=${PR_TITLE}"
echo "NETLIFY_SITE_PREFIX=${{ env.NETLIFY_SITE_PREFIX }}"
echo "BUILD_DIR=${{ env.BUILD_DIR }}"
} >> pr.env
tar --zstd -cf artifact.tar.zst pr.env ${{ env.BUILD_DIR }}
- name: 'Upload artifact for workflow transfer' - name: 'Upload artifact for workflow transfer'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: preview-build name: preview-build
path: artifact.tar.zst path: ${{ env.BUILD_DIR }}
retention-days: 1 retention-days: 1

View File

@ -32,7 +32,7 @@ jobs:
with: with:
submodules: recursive submodules: recursive
# Can potentially be replaced by: `${{ hashFiles('target/**', 'Dockerfile', 'VERSION') }}` # Can potentially be replaced by: `${{ hashFiles('target/**', 'Dockerfile') }}`
# 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,10 +40,7 @@ jobs:
id: derive-image-cache-key id: derive-image-cache-key
shell: bash shell: bash
run: | run: |
ADDITIONAL_FILES=( ADDITIONAL_FILES=( 'Dockerfile' )
'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
@ -74,16 +71,16 @@ jobs:
cache-buildx- cache-buildx-
- name: 'Set up QEMU' - name: 'Set up QEMU'
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.6.0
with: with:
platforms: arm64 platforms: arm64
- name: 'Set up Docker Buildx' - name: 'Set up Docker Buildx'
uses: docker/setup-buildx-action@v3.3.0 uses: docker/setup-buildx-action@v3.10.0
# 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@v5.3.0 uses: docker/build-push-action@v6.15.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.5.1 uses: docker/metadata-action@v5.7.0
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.0.0 uses: docker/setup-qemu-action@v3.6.0
with: with:
platforms: arm64 platforms: arm64
- name: 'Set up Docker Buildx' - name: 'Set up Docker Buildx'
uses: docker/setup-buildx-action@v3.3.0 uses: docker/setup-buildx-action@v3.10.0
# 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@v5.3.0 uses: docker/build-push-action@v6.15.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.3.0 uses: docker/setup-buildx-action@v3.10.0
# 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@v5.3.0 uses: docker/build-push-action@v6.15.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.3.0 uses: docker/setup-buildx-action@v3.10.0
# 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@v5.3.0 uses: docker/build-push-action@v6.15.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@v3.6.4 uses: anchore/scan-action@v6.1.0
id: scan id: scan
with: with:
image: mailserver-testing:ci image: mailserver-testing:ci

View File

@ -7,10 +7,11 @@ set -ex
# `build --strict` ensures the build fails when any warnings are omitted. # `build --strict` ensures the build fails when any warnings are omitted.
docker run \ docker run \
--rm \ --rm \
--quiet \
--user "$(id -u):$(id -g)" \ --user "$(id -u):$(id -g)" \
--volume "${PWD}:/docs" \ --volume "./:/docs" \
--name "build-docs" \ --name "build-docs" \
squidfunk/mkdocs-material:9.5 build --strict squidfunk/mkdocs-material:9.6 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,10 +2,115 @@
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/v13.3.1...HEAD) ## [Unreleased](https://github.com/docker-mailserver/docker-mailserver/compare/v15.0.2...HEAD)
> **Note**: Changes and additions listed here are contained in the `:edge` image tag. These changes may not be as stable as released changes. > **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
- **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))
- 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.
- 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.**
- **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`_).
- 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))
### Added
- **Internal:**
- 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
- **Internal:**
- **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))
- During image build, ensure a secure connection when downloading the `fail2ban` package ([#4080](https://github.com/docker-mailserver/docker-mailserver/pull/4080))
- **Documentation:**
- Account Management and Authentication pages have been rewritten and better organized ([#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))
- **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:**
- By default opt-out from _Microsoft reactions_ for outbound mail ([#4120](https://github.com/docker-mailserver/docker-mailserver/pull/4120))
- **Rspamd:**
- Updated 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
- **Dovecot:**
- The logwatch `ignore.conf` now also excludes 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))
- 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))
- **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))
- **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))
- `start-mailserver.sh` removed unused `shopt -s inherit_errexit` ([#4161](https://github.com/docker-mailserver/docker-mailserver/pull/4161))
- 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))
- 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:**
- 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
- Removed `CONTRIBUTORS.md`, `.all-contributorsrc`, and workflow ([#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))
## [v14.0.0](https://github.com/docker-mailserver/docker-mailserver/releases/tag/v14.0.0)
The most noteworthy change of this release is the update of the container's base image from Debian 11 ("Bullseye") to Debian 12 ("Bookworm"). This update alone involves breaking changes and requires a careful update! The most noteworthy change of this release is the update of the container's base image from Debian 11 ("Bullseye") to Debian 12 ("Bookworm"). This update alone involves breaking changes and requires a careful update!
### Breaking ### Breaking
@ -43,7 +148,7 @@ The most noteworthy change of this release is the update of the container's base
- **Removed support for Solr integration:** ([#4025](https://github.com/docker-mailserver/docker-mailserver/pull/4025)) - **Removed support for Solr integration:** ([#4025](https://github.com/docker-mailserver/docker-mailserver/pull/4025))
- This was a community contributed feature for FTS (Full Text Search), the docs advise using an image that has not been maintained for over 2 years and lacks ARM64 support. Based on user engagement over the years this feature has very niche value to continue to support, thus is being removed. - This was a community contributed feature for FTS (Full Text Search), the docs advise using an image that has not been maintained for over 2 years and lacks ARM64 support. Based on user engagement over the years this feature has very niche value to continue to support, thus is being removed.
- If you use Solr, support can be restored if you're willing to contribute docs for the feature that resolves the concerns raised - If you use Solr, support can be restored if you're willing to contribute docs for the feature that resolves the concerns raised
- **Log**: - **Log:**
- The format of DMS specific logs (_from our scripts, not running services_) has been changed. The new format is `<RFC 3339 TIMESTAMP> <LOG LEVEL> <LOG EVENT SRC>: <MESSAGE>` ([#4035](https://github.com/docker-mailserver/docker-mailserver/pull/4035)) - The format of DMS specific logs (_from our scripts, not running services_) has been changed. The new format is `<RFC 3339 TIMESTAMP> <LOG LEVEL> <LOG EVENT SRC>: <MESSAGE>` ([#4035](https://github.com/docker-mailserver/docker-mailserver/pull/4035))
- **rsyslog:** - **rsyslog:**
- Debian 12 adjusted the `rsyslog` configuration for the default file template from `RSYSLOG_TraditionalFileFormat` to `RSYSLOG_FileFormat` (_upstream default since 2012_). This change may affect you if you have any monitoring / analysis of log output (_eg: `mail.log` / `docker logs`_). - Debian 12 adjusted the `rsyslog` configuration for the default file template from `RSYSLOG_TraditionalFileFormat` to `RSYSLOG_FileFormat` (_upstream default since 2012_). This change may affect you if you have any monitoring / analysis of log output (_eg: `mail.log` / `docker logs`_).
@ -60,7 +165,7 @@ The most noteworthy change of this release is the update of the container's base
- `smtp_sasl_auth_enable = yes` (_SASL auth to outbound MTA connections is enabled_) - `smtp_sasl_auth_enable = yes` (_SASL auth to outbound MTA connections is enabled_)
- `smtp_sasl_security_options = noanonymous` (_credentials are mandatory for outbound mail delivery_) - `smtp_sasl_security_options = noanonymous` (_credentials are mandatory for outbound mail delivery_)
- `smtp_tls_security_level = encrypt` (_the outbound MTA connection must always be secure due to credentials sent_) - `smtp_tls_security_level = encrypt` (_the outbound MTA connection must always be secure due to credentials sent_)
- **Environment Variables**: - **Environment Variables:**
- `SA_SPAM_SUBJECT` has been renamed into `SPAM_SUBJECT` to become anti-spam service agnostic. ([#3820](https://github.com/docker-mailserver/docker-mailserver/pull/3820)) - `SA_SPAM_SUBJECT` has been renamed into `SPAM_SUBJECT` to become anti-spam service agnostic. ([#3820](https://github.com/docker-mailserver/docker-mailserver/pull/3820))
- As this functionality is now handled in Dovecot via a Sieve script instead of the respective anti-spam service during Postfix processing, this feature will only apply to mail stored in Dovecot. If you have relied on this feature in a different context, it will no longer be available. - As this functionality is now handled in Dovecot via a Sieve script instead of the respective anti-spam service during Postfix processing, this feature will only apply to mail stored in Dovecot. If you have relied on this feature in a different context, it will no longer be available.
- Rspamd previously handled this functionality via the `rewrite_subject` action which as now been disabled by default in favor of the new approach with `SPAM_SUBJECT`. - Rspamd previously handled this functionality via the `rewrite_subject` action which as now been disabled by default in favor of the new approach with `SPAM_SUBJECT`.
@ -68,16 +173,16 @@ The most noteworthy change of this release is the update of the container's base
- The default has changed to not prepend any prefix to the subject unless configured to do so. If you relied on the implicit prefix, you will now need to provide one explicitly. - The default has changed to not prepend any prefix to the subject unless configured to do so. If you relied on the implicit prefix, you will now need to provide one explicitly.
- `undef` was previously supported as an opt-out with `SA_SPAM_SUBJECT`. This is no longer valid, the equivalent opt-out value is now an empty value (_or rather the omission of this ENV being configured_). - `undef` was previously supported as an opt-out with `SA_SPAM_SUBJECT`. This is no longer valid, the equivalent opt-out value is now an empty value (_or rather the omission of this ENV being configured_).
- The feature to include [`_SCORE_` tag](https://spamassassin.apache.org/full/4.0.x/doc/Mail_SpamAssassin_Conf.html#rewrite_header-subject-from-to-STRING) in your value to be replaced by the associated spam score is no longer available. - The feature to include [`_SCORE_` tag](https://spamassassin.apache.org/full/4.0.x/doc/Mail_SpamAssassin_Conf.html#rewrite_header-subject-from-to-STRING) in your value to be replaced by the associated spam score is no longer available.
- **Supervisord**: - **Supervisord:**
- `supervisor-app.conf` renamed to `dms-services.conf` - `supervisor-app.conf` renamed to `dms-services.conf` ([#3908](https://github.com/docker-mailserver/docker-mailserver/pull/3908))
- **Rspamd**: - **Rspamd:**
- the Redis history key has been changed in order to not incorporate the hostname of the container (which is desirable in Kubernetes environments) ([#3927](https://github.com/docker-mailserver/docker-mailserver/pull/3927)) - The Redis history key has been changed in order to not incorporate the hostname of the container (which is desirable in Kubernetes environments) ([#3927](https://github.com/docker-mailserver/docker-mailserver/pull/3927))
- **Account Management** - **Account Management:**
- addresses (accounts) are now normalized to lowercase automatically and a warning is logged in case uppercase letters are supplied - Addresses (accounts) are now normalized to lowercase automatically and a warning is logged in case uppercase letters are supplied ([#4033](https://github.com/docker-mailserver/docker-mailserver/pull/4033))
### Added ### Added
- **Docs:** - **Documentation:**
- A guide for configuring a public server to relay inbound and outbound mail from DMS on a private server ([#3973](https://github.com/docker-mailserver/docker-mailserver/pull/3973)) - A guide for configuring a public server to relay inbound and outbound mail from DMS on a private server ([#3973](https://github.com/docker-mailserver/docker-mailserver/pull/3973))
- Added information on how to configure send-only aliases ([#4044](https://github.com/docker-mailserver/docker-mailserver/pull/4044)) - Added information on how to configure send-only aliases ([#4044](https://github.com/docker-mailserver/docker-mailserver/pull/4044))
- **Environment Variables:** - **Environment Variables:**
@ -98,7 +203,7 @@ The most noteworthy change of this release is the update of the container's base
- Enable spamassassin only, when amavis is enabled too. ([#3943](https://github.com/docker-mailserver/docker-mailserver/pull/3943)) - Enable spamassassin only, when amavis is enabled too. ([#3943](https://github.com/docker-mailserver/docker-mailserver/pull/3943))
- **Tests:** - **Tests:**
- Refactored helper methods for sending e-mails with specific `Message-ID` headers and the helpers for retrieving + filtering logs, which together help isolate logs relevant to specific mail when multiple mails have been processed within a single test. ([#3786](https://github.com/docker-mailserver/docker-mailserver/pull/3786)) - Refactored helper methods for sending e-mails with specific `Message-ID` headers and the helpers for retrieving + filtering logs, which together help isolate logs relevant to specific mail when multiple mails have been processed within a single test. ([#3786](https://github.com/docker-mailserver/docker-mailserver/pull/3786))
- **Rspamd**: - **Rspamd:**
- The `rewrite_subject` action, is now disabled by default. It has been replaced with the new `SPAM_SUBJECT` environment variable, which implements the functionality via a Sieve script instead which is anti-spam service agnostic ([#3820](https://github.com/docker-mailserver/docker-mailserver/pull/3820)) - The `rewrite_subject` action, is now disabled by default. It has been replaced with the new `SPAM_SUBJECT` environment variable, which implements the functionality via a Sieve script instead which is anti-spam service agnostic ([#3820](https://github.com/docker-mailserver/docker-mailserver/pull/3820))
- `RSPAMD_NEURAL` was added and is disabled by default. If switched on it will enable the experimental Rspamd "Neural network" module to add a layer of analysis to spam detection ([#3833](https://github.com/docker-mailserver/docker-mailserver/pull/3833)) - `RSPAMD_NEURAL` was added and is disabled by default. If switched on it will enable the experimental Rspamd "Neural network" module to add a layer of analysis to spam detection ([#3833](https://github.com/docker-mailserver/docker-mailserver/pull/3833))
- The symbol weights of SPF, DKIM and DMARC have been adjusted again. Fixes a bug and includes more appropriate combinations of symbols ([#3913](https://github.com/docker-mailserver/docker-mailserver/pull/3913), [#3923](https://github.com/docker-mailserver/docker-mailserver/pull/3923)) - The symbol weights of SPF, DKIM and DMARC have been adjusted again. Fixes a bug and includes more appropriate combinations of symbols ([#3913](https://github.com/docker-mailserver/docker-mailserver/pull/3913), [#3923](https://github.com/docker-mailserver/docker-mailserver/pull/3923))
@ -148,12 +253,12 @@ The most noteworthy change of this release is the update of the container's base
### Added ### Added
- **Docs:** - **Documentation:**
- An example for how to bind outbound SMTP connections to a specific network interface ([#3465](https://github.com/docker-mailserver/docker-mailserver/pull/3465)) - An example for how to bind outbound SMTP connections to a specific network interface ([#3465](https://github.com/docker-mailserver/docker-mailserver/pull/3465))
### Updates ### Updates
- **Tests**: - **Tests:**
- Revised OAuth2 test ([#3795](https://github.com/docker-mailserver/docker-mailserver/pull/3795)) - Revised OAuth2 test ([#3795](https://github.com/docker-mailserver/docker-mailserver/pull/3795))
- Replace `wc -l` with `grep -c` ([#3752](https://github.com/docker-mailserver/docker-mailserver/pull/3752)) - Replace `wc -l` with `grep -c` ([#3752](https://github.com/docker-mailserver/docker-mailserver/pull/3752))
- Revised testing of service process management (supervisord) to be more robust ([#3780](https://github.com/docker-mailserver/docker-mailserver/pull/3780)) - Revised testing of service process management (supervisord) to be more robust ([#3780](https://github.com/docker-mailserver/docker-mailserver/pull/3780))
@ -165,9 +270,9 @@ The most noteworthy change of this release is the update of the container's base
- `test/files/emails/existing/` files were removed similar to previous removal of SMTP auth files as they became redundant with `swaks`. - `test/files/emails/existing/` files were removed similar to previous removal of SMTP auth files as they became redundant with `swaks`.
- **Internal:** - **Internal:**
- Postfix is now configured with `smtputf8_enable = no` in our default `main.cf` config (_instead of during container startup_). ([#3750](https://github.com/docker-mailserver/docker-mailserver/pull/3750)) - Postfix is now configured with `smtputf8_enable = no` in our default `main.cf` config (_instead of during container startup_). ([#3750](https://github.com/docker-mailserver/docker-mailserver/pull/3750))
- **Rspamd** ([#3726](https://github.com/docker-mailserver/docker-mailserver/pull/3726)): - **Rspamd:** ([#3726](https://github.com/docker-mailserver/docker-mailserver/pull/3726)):
- Symbol scores for SPF, DKIM & DMARC were updated to more closely align with [RFC7489](https://www.rfc-editor.org/rfc/rfc7489#page-24). Please note that complete alignment is undesirable as other symbols may be added as well, which changes the overall score calculation again, see [this issue](https://github.com/docker-mailserver/docker-mailserver/issues/3690#issuecomment-1866871996) - Symbol scores for SPF, DKIM & DMARC were updated to more closely align with [RFC7489](https://www.rfc-editor.org/rfc/rfc7489#page-24). Please note that complete alignment is undesirable as other symbols may be added as well, which changes the overall score calculation again, see [this issue](https://github.com/docker-mailserver/docker-mailserver/issues/3690#issuecomment-1866871996)
- **Docs:** - **Documentation:**
- Revised the SpamAssassin ENV docs to better communicate configuration and their relation to other ENV settings. ([#3756](https://github.com/docker-mailserver/docker-mailserver/pull/3756)) - Revised the SpamAssassin ENV docs to better communicate configuration and their relation to other ENV settings. ([#3756](https://github.com/docker-mailserver/docker-mailserver/pull/3756))
- Detailed how mail received is assigned a spam score by Rspamd and processed accordingly ([#3773](https://github.com/docker-mailserver/docker-mailserver/pull/3773)) - Detailed how mail received is assigned a spam score by Rspamd and processed accordingly ([#3773](https://github.com/docker-mailserver/docker-mailserver/pull/3773))
@ -216,7 +321,7 @@ DMS is now secured against the [recently published spoofing attack "SMTP Smuggli
- ENV `ENABLE_IMAP` ([#3703](https://github.com/docker-mailserver/docker-mailserver/pull/3703)) - ENV `ENABLE_IMAP` ([#3703](https://github.com/docker-mailserver/docker-mailserver/pull/3703))
- **Tests:** - **Tests:**
- You can now use `make run-local-instance` to run a DMS image that was built locally to test changes ([#3663](https://github.com/docker-mailserver/docker-mailserver/pull/3663)) - You can now use `make run-local-instance` to run a DMS image that was built locally to test changes ([#3663](https://github.com/docker-mailserver/docker-mailserver/pull/3663))
- **Internal**: - **Internal:**
- Log a warning when update-check is enabled, but no stable release image is used ([#3684](https://github.com/docker-mailserver/docker-mailserver/pull/3684)) - Log a warning when update-check is enabled, but no stable release image is used ([#3684](https://github.com/docker-mailserver/docker-mailserver/pull/3684))
### Updates ### Updates
@ -231,7 +336,7 @@ DMS is now secured against the [recently published spoofing attack "SMTP Smuggli
### Fixed ### Fixed
- **Internal**: - **Internal:**
- The container startup welcome log message now references `DMS_RELEASE` ([#3676](https://github.com/docker-mailserver/docker-mailserver/pull/3676)) - The container startup welcome log message now references `DMS_RELEASE` ([#3676](https://github.com/docker-mailserver/docker-mailserver/pull/3676))
- `VERSION` was incremented for prior releases to be notified of the v13.0.1 patch release ([#3676](https://github.com/docker-mailserver/docker-mailserver/pull/3676)) - `VERSION` was incremented for prior releases to be notified of the v13.0.1 patch release ([#3676](https://github.com/docker-mailserver/docker-mailserver/pull/3676))
- `VERSION` is no longer included in the image ([#3711](https://github.com/docker-mailserver/docker-mailserver/pull/3711)) - `VERSION` is no longer included in the image ([#3711](https://github.com/docker-mailserver/docker-mailserver/pull/3711))

File diff suppressed because it is too large Load Diff

View File

@ -62,7 +62,7 @@ SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
# which would require an extra memory of 500MB+ during an image build. # which would require an extra memory of 500MB+ during an image build.
# When using `COPY --link`, the `--chown` option is only compatible with numeric ID values. # When using `COPY --link`, the `--chown` option is only compatible with numeric ID values.
# hadolint ignore=DL3021 # hadolint ignore=DL3021
COPY --link --chown=200 --from=docker.io/clamav/clamav:latest /var/lib/clamav /var/lib/clamav COPY --link --chown=200 --from=docker.io/clamav/clamav-debian:latest /var/lib/clamav /var/lib/clamav
RUN <<EOF RUN <<EOF
# `COPY --link --chown=200` has a bug when built by the buildx docker-container driver. # `COPY --link --chown=200` has a bug when built by the buildx docker-container driver.
@ -82,22 +82,12 @@ EOF
# install fts_xapian plugin # install fts_xapian plugin
COPY --from=stage-compile dovecot-fts-xapian-1.7.12_1.7.12_*.deb / COPY --from=stage-compile dovecot-fts-xapian-*.deb /
RUN dpkg -i /dovecot-fts-xapian-1.7.12_1.7.12_*.deb && rm /dovecot-fts-xapian-1.7.12_1.7.12_*.deb 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 ------------------------------------
@ -109,14 +99,15 @@ COPY target/rspamd/local.d/ /etc/rspamd/local.d/
# --- OAUTH2 ------------------------------------ # --- OAUTH2 ------------------------------------
# ----------------------------------------------- # -----------------------------------------------
COPY target/dovecot/auth-oauth2.conf.ext /etc/dovecot/conf.d
COPY target/dovecot/dovecot-oauth2.conf.ext /etc/dovecot COPY target/dovecot/dovecot-oauth2.conf.ext /etc/dovecot
COPY target/dovecot/auth-oauth2.conf.ext /etc/dovecot/conf.d
# ----------------------------------------------- # -----------------------------------------------
# --- LDAP & SpamAssassin's Cron ---------------- # --- LDAP & SpamAssassin's Cron ----------------
# ----------------------------------------------- # -----------------------------------------------
COPY target/dovecot/dovecot-ldap.conf.ext /etc/dovecot COPY target/dovecot/dovecot-ldap.conf.ext /etc/dovecot
COPY target/dovecot/auth-ldap.conf.ext /etc/dovecot/conf.d
COPY \ COPY \
target/postfix/ldap-users.cf \ target/postfix/ldap-users.cf \
target/postfix/ldap-groups.cf \ target/postfix/ldap-groups.cf \
@ -185,8 +176,6 @@ COPY target/fail2ban/fail2ban.d/fixes.local /etc/fail2ban/fail2ban.d/fixes.local
RUN <<EOF RUN <<EOF
ln -s /var/log/mail/mail.log /var/log/mail.log ln -s /var/log/mail/mail.log /var/log/mail.log
ln -sf /var/log/mail/fail2ban.log /var/log/fail2ban.log ln -sf /var/log/mail/fail2ban.log /var/log/fail2ban.log
# disable sshd jail
rm /etc/fail2ban/jail.d/defaults-debian.conf
EOF EOF
COPY target/opendkim/opendkim.conf /etc/opendkim.conf COPY target/opendkim/opendkim.conf /etc/opendkim.conf
@ -214,7 +203,8 @@ EOF
RUN echo 'Reason_Message = Message {rejectdefer} due to: {spf}.' >>/etc/postfix-policyd-spf-python/policyd-spf.conf RUN echo 'Reason_Message = Message {rejectdefer} due to: {spf}.' >>/etc/postfix-policyd-spf-python/policyd-spf.conf
COPY target/fetchmail/fetchmailrc /etc/fetchmailrc_general COPY target/fetchmail/fetchmailrc /etc/fetchmailrc_general
COPY target/getmail/getmailrc /etc/getmailrc_general COPY target/getmail/getmailrc_general /etc/getmailrc_general
COPY target/getmail/getmail-service.sh /usr/local/bin/
COPY target/postfix/main.cf target/postfix/master.cf /etc/postfix/ COPY target/postfix/main.cf target/postfix/master.cf /etc/postfix/
# DH parameters for DHE cipher suites, ffdhe4096 is the official standard 4096-bit DH params now part of TLS 1.3 # DH parameters for DHE cipher suites, ffdhe4096 is the official standard 4096-bit DH params now part of TLS 1.3

View File

@ -38,8 +38,8 @@ 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/v13.3/config/user-management/#address-tags-extension-delimiters-as-an-alternative-to-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/v13.3/config/user-management/#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/account-management/overview/#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

View File

@ -1 +0,0 @@
13.3.1

View File

@ -1,3 +1,5 @@
# https://getmail6.org/configuration.html#conf-options
[options] [options]
verbose = 0 verbose = 0
read_all = false read_all = false
@ -5,3 +7,5 @@ delete = false
max_messages_per_session = 500 max_messages_per_session = 500
received = false received = false
delivered_to = false delivered_to = false
message_log_syslog = true

View File

@ -0,0 +1,13 @@
# https://getmail6.org/configuration.html
[retriever]
type = SimpleIMAPSSLRetriever
server = imap.gmail.com
username = alice
password = notsecure
[destination]
type = MDA_external
path = /usr/lib/dovecot/deliver
allow_root_commands = true
arguments =("-d","user1@example.com")

View File

@ -0,0 +1,13 @@
# https://getmail6.org/configuration.html
[retriever]
type = SimplePOP3SSLRetriever
server = pop3.gmail.com
username = alice
password = notsecure
[destination]
type = MDA_external
path = /usr/lib/dovecot/deliver
allow_root_commands = true
arguments =("-d","user1@example.com")

View File

@ -0,0 +1,60 @@
# Docs: https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/mail-fetchmail
# Additional context, with CLI commands for verification:
# https://github.com/orgs/docker-mailserver/discussions/3994#discussioncomment-9290570
services:
dms-fetch:
image: ghcr.io/docker-mailserver/docker-mailserver:latest # :15.0
hostname: mail.example.test
environment:
ENABLE_FETCHMAIL: 1
# We change this setting to 10 for quicker testing:
FETCHMAIL_POLL: 10
# Link the DNS lookup `remote.test` to resolve to the `dms-remote` container IP (for `@remote.test` address):
# This is only for this example, since no real DNS service is configured, this is a Docker internal DNS feature:
links:
- "dms-remote:remote.test"
# NOTE: Optional, You only need to publish ports if you want to verify via your own mail client.
#ports:
# - "465:465" # ESMTP (implicit TLS)
# - "993:993" # IMAP4 (implicit TLS)
# You'd normally use `volumes` here but for simplicity of the example, all config is contained within `compose.yaml`:
configs:
- source: dms-accounts-fetch
target: /tmp/docker-mailserver/postfix-accounts.cf
- source: fetchmail
target: /tmp/docker-mailserver/fetchmail.cf
dms-remote:
image: ghcr.io/docker-mailserver/docker-mailserver:latest # :15.0
hostname: mail.remote.test
environment:
# Allows for us send a test mail easily by trusting any mail client run within this container (`swaks`):
PERMIT_DOCKER: container
# Alternatively, trust and accept any mail received from clients in same subnet of dms-fetch:
#PERMIT_DOCKER: connected-networks
configs:
- source: dms-accounts-remote
target: /tmp/docker-mailserver/postfix-accounts.cf
# Using the Docker Compose `configs.content` feature instead of volume mounting separate files.
# NOTE: This feature requires Docker Compose v2.23.1 (Nov 2023) or newer:
# https://github.com/compose-spec/compose-spec/pull/446
configs:
fetchmail:
content: |
poll 'mail.remote.test' proto imap
user 'jane.doe@remote.test'
pass 'secret'
is 'john.doe@example.test'
no sslcertck
# DMS requires an account to complete setup, configure one for each instance:
# NOTE: Both accounts are configured with the same password (SHA512-CRYPT hashed), `secret`.
dms-accounts-fetch:
content: |
john.doe@example.test|{SHA512-CRYPT}$$6$$sbgFRCmQ.KWS5ryb$$EsWrlYosiadgdUOxCBHY0DQ3qFbeudDhNMqHs6jZt.8gmxUwiLVy738knqkHD4zj4amkb296HFqQ3yDq4UXt8.
dms-accounts-remote:
content: |
jane.doe@remote.test|{SHA512-CRYPT}$$6$$sbgFRCmQ.KWS5ryb$$EsWrlYosiadgdUOxCBHY0DQ3qFbeudDhNMqHs6jZt.8gmxUwiLVy738knqkHD4zj4amkb296HFqQ3yDq4UXt8.

View File

@ -0,0 +1,147 @@
# Docs: https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/mail-forwarding/relay-hosts/
# Additional context, with CLI commands for verification:
# https://github.com/docker-mailserver/docker-mailserver/issues/4136#issuecomment-2253693490
services:
# This would represent your actual DMS container:
dms-sender:
image: mailserver/docker-mailserver:latest # :15.0
hostname: mail.example.test
environment:
# All outbound mail will be relayed through this host
# (change the port to 587 if you do not want the postfix-main.cf override)
- DEFAULT_RELAY_HOST=[smtp.relay-service.test]:465
# Your relay host credentials.
# (since the relay in the example is DMS, the relay account username is a full email address)
- RELAY_USER=relay-user@relay-service.test
- RELAY_PASSWORD=secret
# The mail client (swaks) needs to connect with TLS:
- SSL_TYPE=manual
- SSL_KEY_PATH=/tmp/tls/key.pem
- SSL_CERT_PATH=/tmp/tls/cert.pem
# You would usually have `volumes` instead of this `configs`:
configs:
- source: dms-main
target: /tmp/docker-mailserver/postfix-main.cf
- source: dms-accounts
target: /tmp/docker-mailserver/postfix-accounts.cf
# Authenticating on port 587 or 465 enforces TLS requirement:
- source: tls-cert
target: /tmp/tls/cert.pem
- source: tls-key
target: /tmp/tls/key.pem
# This is only needed if you want to verify the TLS cert chain with swaks
# (normally with public CA providers like LetsEncrypt this file is already available to a mail client)
- source: tls-ca-cert
target: /tmp/tls/ca-cert.pem
# Pretend this is your third-party relay service:
dms-relay:
image: mailserver/docker-mailserver:latest # :15.0
hostname: smtp.relay-service.test
environment:
# WORKAROUND: Bypass security checks from the mail-client (dms-sender container)
# (avoids needing expected DNS records to run this example)
- PERMIT_DOCKER=connected-networks
# 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)
- SSL_TYPE=manual
- SSL_KEY_PATH=/tmp/tls/key.pem
- SSL_CERT_PATH=/tmp/tls/cert.pem
configs:
- source: dms-accounts-relay
target: /tmp/docker-mailserver/postfix-accounts.cf
- source: tls-cert
target: /tmp/tls/cert.pem
- source: tls-key
target: /tmp/tls/key.pem
# Pretend this is another mail server that your target recipient belongs to (like Gmail):
dms-destination:
image: mailserver/docker-mailserver:latest # :15.0
hostname: mail.destination.test
# WORKAROUND: dms-relay must be able to resolve DNS for `@destination.test` to the IP of this container:
# Normally a MX record would direct mail to the MTA (eg: `mail.destination.test`)
networks:
default:
aliases:
- destination.test
environment:
# WORKAROUND: Same workaround as needed for dms-relay
- PERMIT_DOCKER=connected-networks
configs:
- source: dms-accounts-destination
target: /tmp/docker-mailserver/postfix-accounts.cf
# Using the Docker Compose `configs.content` feature instead of volume mounting separate files.
# NOTE: This feature requires Docker Compose v2.23.1 (Nov 2023) or newer:
# https://github.com/compose-spec/compose-spec/pull/446
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.
# Login credentials:
# user: "john.doe@example.test" password: "secret"
# user: "relay-user@relay-service.test" password: "secret"
# user: "jane.doe@destination.test" password: "secret"
dms-accounts:
# NOTE: `$` needed to be repeated to escape it,
# which opts out of the `compose.yaml` variable interpolation feature.
content: |
john.doe@example.test|{SHA512-CRYPT}$$6$$sbgFRCmQ.KWS5ryb$$EsWrlYosiadgdUOxCBHY0DQ3qFbeudDhNMqHs6jZt.8gmxUwiLVy738knqkHD4zj4amkb296HFqQ3yDq4UXt8.
dms-accounts-relay:
content: |
relay-user@relay-service.test|{SHA512-CRYPT}$$6$$o65y1ZXC4ooOPLwZ$$7TF1nYowEtNJpH6BwJBgdj2pPAxaCvhIKQA6ww5zdHm/AA7aemY9eoHC91DOgYNaKj1HLxSeWNDdvrp6mbtUY.
dms-accounts-destination:
content: |
jane.doe@destination.test|{SHA512-CRYPT}$$6$$o65y1ZXC4ooOPLwZ$$7TF1nYowEtNJpH6BwJBgdj2pPAxaCvhIKQA6ww5zdHm/AA7aemY9eoHC91DOgYNaKj1HLxSeWNDdvrp6mbtUY.
# TLS files:
# - 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
# `swaks` run in the container will need to reference this CA cert file for successful verficiation (optional).
tls-ca-cert:
content: |
-----BEGIN CERTIFICATE-----
MIIBfTCCASKgAwIBAgIRAMAZttlRlkcuSun0yV0z4RwwCgYIKoZIzj0EAwIwHDEa
MBgGA1UEAxMRU21hbGxzdGVwIFJvb3QgQ0EwHhcNMjEwMTAxMDAwMDAwWhcNMzEw
MTAxMDAwMDAwWjAcMRowGAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTBZMBMGByqG
SM49AgEGCCqGSM49AwEHA0IABJX2hCtoK3+bM5I3rmyApXLJ1gOcVhtoSSwM8XXR
SEl25Kkc0n6mINuMK8UrBkiBUgexf6CYayx3xVr9TmMkg4KjRTBDMA4GA1UdDwEB
/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBQD8sBrApbyYyqU
y+/TlwGynx2V5jAKBggqhkjOPQQDAgNJADBGAiEAi8N2eOETI+6hY3+G+kzNMd3K
Sd3Ke8b++/nlwr5Fb/sCIQDYAjpKp/MpTDWICeHC2tcB5ptxoTdWkTBuG4rKcktA
0w==
-----END CERTIFICATE-----
tls-key:
content: |
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOc6wqZmSDmT336K4O26dMk1RCVc0+cmnsO2eK4P5K5yoAoGCCqGSM49
AwEHoUQDQgAEFOWNgekKKvUZE89vJ7henUYxODYIvCiHitRc2ylwttjqt1KUY1cp
q3jof2fhURHfBUH3dHPXLHig5V9Jw5gqeg==
-----END EC PRIVATE KEY-----
tls-cert:
content: |
-----BEGIN CERTIFICATE-----
MIIB9DCCAZqgAwIBAgIQE53a/y2c//YXRsz2kLm6gDAKBggqhkjOPQQDAjAcMRow
GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0yMTAxMDEwMDAwMDBaFw0zMTAx
MDEwMDAwMDBaMBkxFzAVBgNVBAMTDlNtYWxsc3RlcCBMZWFmMFkwEwYHKoZIzj0C
AQYIKoZIzj0DAQcDQgAEFOWNgekKKvUZE89vJ7henUYxODYIvCiHitRc2ylwttjq
t1KUY1cpq3jof2fhURHfBUH3dHPXLHig5V9Jw5gqeqOBwDCBvTAOBgNVHQ8BAf8E
BAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSz
w74g+O6dcBbwienD70D8A9ESmDAfBgNVHSMEGDAWgBQD8sBrApbyYyqUy+/TlwGy
nx2V5jBMBgNVHREERTBDghFtYWlsLmV4YW1wbGUudGVzdIIVbWFpbC5kZXN0aW5h
dGlvbi50ZXN0ghdzbXRwLnJlbGF5LXNlcnZpY2UudGVzdDAKBggqhkjOPQQDAgNI
ADBFAiEAoety5oClZtuBMkvlUIWRmWlyg1VIOZ544LSEbplsIhcCIHb6awMwNdXP
m/xHjFkuwH1+UjDDRW53Ih7KZoLrQ6Cp
-----END CERTIFICATE-----

View File

@ -0,0 +1,252 @@
# Account Management - Overview
This page provides a technical reference for account management in DMS.
!!! note "Account provisioners and alternative authentication support"
Each [`ACCOUNT_PROVISIONER`][docs::env::account-provisioner] has a separate page for configuration guidance and caveats:
- [`FILE` provisioner docs][docs::account-provisioner::file]
- [`LDAP` provisioner docs][docs::account-provisioner::ldap]
Authentication from the provisioner can be supplemented with additional methods:
- [OAuth2 / OIDC][docs::account-auth::oauth2] (_allow login from an external authentication service_)
- [Master Accounts][docs::account-auth::master-accounts] (_access the mailbox of any DMS account_)
---
For custom authentication requirements, you could [implement this with Lua][docs::examples::auth-lua].
## Accounts
!!! info
To receive or send mail, you'll need to provision user accounts into DMS (_as each provisioner page documents_).
---
A DMS account represents a user with their _login username_ + password, and optional config like aliases and quota.
- Sending mail from different addresses **does not require** aliases or separate accounts.
- Each account is configured with a _primary email address_ that a mailbox is associated to.
??? info "Primary email address"
The email address associated to an account creates a mailbox. This address is relevant:
- When DMS **receives mail** for that address as the recipient (_or an alias that resolves to it_), to identify which mailbox to deliver into.
- With **mail submission**:
- `SPOOF_PROTECTION=1` **restricts the sender address** to the DMS account email address (_unless additional sender addresses have been permitted via supported config_).
- `SPOOF_PROTECTION=0` allows DMS accounts to **use any sender address** (_only a single DMS account is necessary to send mail with different sender addresses_).
---
For more details, see the [Technical Overview](#technical-overview) section.
??? note "Support for multiple mail domains"
No extra configuration in DMS is required after provisioning an account with an email address.
- The DNS records for a domain should direct mail to DMS and allow DMS to send mail on behalf of that domain.
- DMS does not need TLS certificates for your mail domains, only for the DMS FQDN (_the `hostname` setting_).
??? warning "Choosing a compatible email address"
An email address should conform to the standard [permitted charset and format][email-syntax::valid-charset-format] (`local-part@domain-part`).
---
DMS has features that need to reserve special characters to work correctly. Ensure those characters are not present in email addresses you configure for DMS, otherwise disable / opt-out of the feature.
- [Sub-addressing](#sub-addressing) is enabled by default with `+` as the _tag delimiter_. The tag can be changed, feature opt-out when the tag is explicitly unset.
### Aliases
!!! info
Aliases allow receiving mail:
- As an alternative delivery address for a DMS account mailbox.
- To redirect / forward to an external address outside of DMS like `@gmail.com`.
??? abstract "Technical Details (_Local vs Virtual aliases_)"
Aliases are managed through Postfix which supports _local_ and _virtual_ aliases:
- **Local aliases** are for mail routed to the [`local` delivery agent][postfix::delivery-agent::local] (see [associated alias config format][postfix::config-table::local-alias])
- You rarely need to configure this. It is used internally for system unix accounts belonging to the services running in DMS (_including `root`_).
- `postmaster` may be a local alias to `root`, and `root` to a virtual alias or real email address.
- Any mail sent through the `local` delivery agent will not be delivered to an inbox managed by Dovecot (_unless you have configured a local alias to redirect mail to a valid address or alias_).
- The domain-part of an these aliases belongs to your DMS FQDN (_`hostname: mail.example.com`, thus `user@mail.example.com`_). Technically there is no domain-part at this point, that context is used when routing delivery, the local delivery agent only knows of the local-part (_an alias or unix account_).
- [**Virtual aliases**][postfix-docs::virtual-alias] are for mail routed to the [`virtual` delivery agent][postfix::delivery-agent::virtual] (see [associated alias config format][postfix::config-table::virtual-alias])
- When alias support in DMS is discussed without the context of being a local or virtual alias, it's likely the virtual kind (_but could also be agnostic_).
- The domain-part of an these aliases belongs to a mail domain managed by DMS (_like `user@example.com`_).
!!! tip "Verify alias resolves correctly"
You can run `postmap -q <alias> <table>` in the container to verify an alias resolves to the expected target. If the target is also an alias, the command will not expand that alias to resolve the actual recipient(s).
For the `FILE` provisioner, an example would be: `postmap -q alias1@example.com /etc/postfix/virtual`. For the `LDAP` provisioner you'd need to adjust the table path.
!!! info "Side effect - Dovecot Quotas (`ENABLE_QUOTAS=1`)"
As a side effect of the alias workaround for the `FILE` provisioner with this feature, aliases can be used for account login. This is not intentional.
### Quotas
!!! info
Enables mail clients with the capability to query a mailbox for disk-space used and capacity limit.
- This feature is enabled by default, opt-out via [`ENABLE_QUOTAS=0`][docs::env::enable-quotas]
- **Not implemented** for the LDAP provisioner (_PR welcome! View the [feature request for implementation advice][gh-issue::dms-feature-request::dovecot-quotas-ldap]_)
??? tip "How are quotas useful?"
Without quota limits for disk storage, a mailbox could fill up the available storage which would cause delivery failures to all mailboxes.
Quotas help by preventing that abuse, so that only a mailbox exceeding the assigned quota experiences a delivery failure instead of negatively impacting others (_provided disk space is available_).
??? abstract "Technical Details"
The [Dovecot Quotas feature][gh-pr::dms-feature::dovecot-quotas] is configured by enabling the [Dovecot `imap-quota` plugin][dovecot-docs::plugin::imap-quota] and using the [`count` quota backend][dovecot-docs::config::quota-backend-count].
---
**Dovecot workaround for Postfix aliases**
When mail is delivered to DMS, Postfix will query Dovecot with the recipient(s) to verify quota has not been exceeded.
This allows early rejection of mail arriving to DMS, preventing a spammer from taking advantage of a [backscatter][wikipedia::backscatter] source if the mail was accepted by Postfix, only to later be rejected by Dovecot for storage when the quota limit was already reached.
However, Postfix does not resolve aliases until after the incoming mail is accepted.
1. Postfix queries Dovecot (_a [`check_policy_service` restriction tied to the Dovecot `quota-status` service][dms::workaround::dovecot-quotas::notes-1]_) with the recipient (_the alias_).
2. `dovecot: auth: passwd-file(alias@example.com): unknown user` is logged, Postfix is then informed that the recipient mailbox is not full even if it actually was (_since no such user exists in the Dovecot UserDB_).
3. However, when the real mailbox address that the alias would later resolve into does have a quota that exceeded the configured limit, Dovecot will refuse the mail delivery from Postfix which introduces a backscatter source for spammers.
As a [workaround to this problem with the `ENABLE_QUOTAS=1` feature][dms::workaround::dovecot-quotas::summary], DMS will add aliases as fake users into Dovecot UserDB (_that are configured with the same data as the real address the alias would resolve to, thus sharing the same mailbox location and quota limit_). This allows Postfix to properly be aware of an aliased mailbox having exceeded the allowed quota.
**NOTE:** This workaround **only supports** aliases to a single target recipient of a real account address / mailbox.
- Additionally, aliases that resolve to another alias or to an external address would both fail the UserDB lookup, unable to determine if enough storage is available.
- A proper fix would [implement a Postfix policy service][dms::workaround::dovecot-quotas::notes-2] that could correctly resolve aliases to valid entries in the Dovecot UserDB, querying the `quota-status` service and returning that response to Postfix.
## Sub-addressing
!!! info
[Subaddressing][wikipedia::subaddressing] (_aka **Plus Addressing** or **Address Tags**_) is a feature that allows you to receive mail to an address which includes a tag appended to the `local-part` of a valid account address.
- A subaddress has a tag delimiter (_default: `+`_), followed by the tag: `<local-part>+<tag>@<domain-part>`
- The subaddress `user+github@example.com` would deliver mail to the same mailbox as `user@example.com`.
- Tags are dynamic. Anything between the `+` and `@` is understood as the tag, no additional configuration required.
- Only the first occurence of the tag delimiter is recognized. Any additional occurences become part of the tag value itself.
??? tip "When is subaddressing useful?"
A common use-case is to use a unique tag for each service you register your email address with.
- Routing delivery to different folders in your mailbox based on the tag (_via a [Sieve filter][docs::sieve::subaddressing]_).
- Data leaks or bulk sales of email addresses.
- If spam / phishing mail you receive has not removed the tag, you will have better insight into where your address was compromised from.
- When the expected tag is missing, this additionally helps identify bad actors. Especially when mail delivery is routed to subfolders by tag.
- For more use-cases, view the end of [this article][web::subaddress-use-cases].
??? tip "Changing the tag delimiter"
Add `recipient_delimiter = +` to these config override files (_replacing `+` with your preferred delimiter_):
- Postfix: `docker-data/dms/config/postfix-main.cf`
- Dovecot: `docker-data/dms/config/dovecot.cf`
??? tip "Opt-out of subaddressing"
Follow the advice to change the tag delimiter, but instead set an empty value (`recipient_delimiter =`).
??? warning "Only for receiving, not sending"
Do not attempt to send mail from these tagged addresses, they are not equivalent to aliases.
This feature is only intended to be used when a mail client sends to a DMS managed recipient address. While DMS does not restrict the sender address you choose to send mail from (_provided `SPOOF_PROTECTION` has not been enabled_), it is often [forbidden by mail services][ms-exchange-docs::limitations].
??? abstract "Technical Details"
The configured tag delimiter (`+`) allows both Postfix and Dovecot to recognize subaddresses. Without this feature configured, the subaddresses would be considered as separate mail accounts rather than routed to a common account address.
---
Internally DMS has the tag delimiter configured by:
- Applying the Postfix `main.cf` setting: [`recipient_delimiter = +`][postfix-docs::recipient-delimiter]
- Dovecot has the equivalent setting set as `+` by default: [`recipient_delimiter = +`][dovecot-docs::config::recipient-delimiter]
## Technical Overview
!!! info
This section provides insight for understanding how Postfix and Dovecot services are involved. It is intended as a reference for maintainers and contributors.
- **Postfix** - Handles when mail is delivered (inbound) to DMS, or sent (outbound) from DMS.
- **Dovecot** - Manages access and storage for mail delivered to the DMS account mailboxes of your users.
??? abstract "Technical Details - Postfix (Inbound vs Outbound)"
Postfix needs to know how to handle inbound and outbound mail by asking these queries:
=== "Inbound"
- What mail domains is DMS responsible for handling? (_for accepting mail delivered_)
- What are valid mail addresses for those mail domains? (_reject delivery for users that don't exist_)
- Are there any aliases to redirect mail to 1 or more users, or forward to externally?
=== "Outbound"
- When `SPOOF_PROTECTION=1`, how should DMS restrict the sender address? (_eg: Users may only send mail from their associated mailbox address_)
??? abstract "Technical Details - Dovecot (Authentication)"
Dovecot additionally handles authenticating user accounts for sending and retrieving mail:
- Over the ports for IMAP and POP3 connections (_110, 143, 993, 995_).
- As the default configured SASL provider, which Postfix delegates user authentication through (_for the submission(s) ports 465 & 587_). Saslauthd can be configured as an alternative SASL provider.
Dovecot splits all authentication lookups into two categories:
- A [PassDB][dovecot::docs::passdb] lookup most importantly authenticates the user. It may also provide any other necessary pre-login information.
- A [UserDB][dovecot::docs::userdb] lookup retrieves post-login information specific to a user.
[docs::env::account-provisioner]: ../environment.md#account_provisioner
[docs::account-provisioner::file]: ./provisioner/file.md
[docs::account-provisioner::ldap]: ./provisioner/ldap.md
[docs::account-auth::oauth2]: ./supplementary/oauth2.md
[docs::account-auth::master-accounts]: ./supplementary/master-accounts.md
[docs::examples::auth-lua]: ../../examples/use-cases/auth-lua.md
[email-syntax::valid-charset-format]: https://stackoverflow.com/questions/2049502/what-characters-are-allowed-in-an-email-address/2049510#2049510
[postfix-docs::virtual-alias]: http://www.postfix.org/VIRTUAL_README.html#virtual_alias
[postfix-docs::recipient-delimiter]: http://www.postfix.org/postconf.5.html#recipient_delimiter
[dovecot-docs::config::recipient-delimiter]: https://doc.dovecot.org/settings/core/#core_setting-recipient_delimiter
[postfix::delivery-agent::local]: https://www.postfix.org/local.8.html
[postfix::delivery-agent::virtual]: https://www.postfix.org/virtual.8.html
[postfix::config-table::local-alias]: https://www.postfix.org/aliases.5.html
[postfix::config-table::virtual-alias]: https://www.postfix.org/virtual.5.html
[docs::env::enable-quotas]: ../environment.md#enable_quotas
[gh-issue::dms-feature-request::dovecot-quotas-ldap]: https://github.com/docker-mailserver/docker-mailserver/issues/2957
[dovecot-docs::config::quota-backend-count]: https://doc.dovecot.org/configuration_manual/quota/quota_count/#quota-backend-count
[dovecot-docs::plugin::imap-quota]: https://doc.dovecot.org/settings/plugin/imap-quota-plugin/
[gh-pr::dms-feature::dovecot-quotas]: https://github.com/docker-mailserver/docker-mailserver/pull/1469
[wikipedia::backscatter]: https://en.wikipedia.org/wiki/Backscatter_%28email%29
[dms::workaround::dovecot-quotas::notes-1]: https://github.com/docker-mailserver/docker-mailserver/issues/2091#issuecomment-954298788
[dms::workaround::dovecot-quotas::notes-2]: https://github.com/docker-mailserver/docker-mailserver/pull/2248#issuecomment-953754532
[dms::workaround::dovecot-quotas::summary]: https://github.com/docker-mailserver/docker-mailserver/pull/2248#issuecomment-955088677
[docs::sieve::subaddressing]: ../advanced/mail-sieve.md#subaddress-mailbox-routing
[web::subaddress-use-cases]: https://www.codetwo.com/admins-blog/plus-addressing
[wikipedia::subaddressing]: https://en.wikipedia.org/wiki/Email_address#Sub-addressing
[ms-exchange-docs::limitations]: https://learn.microsoft.com/en-us/exchange/recipients-in-exchange-online/plus-addressing-in-exchange-online#using-plus-addresses
[dovecot::docs::passdb]: https://doc.dovecot.org/configuration_manual/authentication/password_databases_passdb
[dovecot::docs::userdb]: https://doc.dovecot.org/configuration_manual/authentication/user_databases_userdb

View File

@ -0,0 +1,206 @@
---
title: 'Account Management | Provisioner (File)'
---
# Provisioner - File
## Management via the `setup` CLI
The best way to manage DMS accounts and related config files is through our `setup` CLI provided within the container.
!!! example "Using the `setup` CLI"
Try the following within the DMS container (`docker exec -it <CONTAINER NAME> bash`):
- Add an account: `setup email add <EMAIL ADDRESS>`
- Add an alias: `setup alias add <FROM ALIAS> <TO TARGET ADDRESS>`
- Learn more about the available subcommands via: `setup help`
```bash
# Starts a basic DMS instance and then shells into the container to use the `setup` CLI:
docker run --rm -itd --name dms --hostname mail.example.com mailserver/docker-mailserver
docker exec -it dms bash
# Create an account:
setup email add hello@example.com your-password-here
# Create an alias:
setup alias add your-alias-here@example.com hello@example.com
# Limit the mailbox capacity to 10 MiB:
setup quota set hello@example.com 10M
```
??? tip "Secure password input"
When you don't provide a password to the command, you will be prompted for one. This avoids the password being captured in your shell history.
```bash
# As you input your password it will not update.
# Press the ENTER key to apply the hidden password input.
$ setup email add hello@example.com
Enter Password:
Confirm Password:
```
!!! note "Account removal via `setup email del`"
When you remove a DMS account with this command, it will also remove any associated aliases and quota.
The command will also prompt for deleting the account mailbox from disk, or can be forced with the `-y` flag.
## Config Reference
These config files belong to the [Config Volume][docs::volumes::config].
### Accounts
!!! info
**Config file:** `docker-data/dms/config/postfix-accounts.cf`
---
The config format is line-based with two fields separated by the delimiter `|`:
- **User:** The primary email address for the account mailbox to use.
- **Password:** A SHA512-CRYPT hash of the account password (_in this example it is `secret`_).
??? tip "Password hash without the `setup email add` command"
A compatible password hash can be generated with:
```bash
doveadm pw -s SHA512-CRYPT -u hello@example.com -p secret
```
!!! example "`postfix-accounts.cf` config file"
In this example DMS manages mail for the domain `example.com`:
```cf title="postfix-accounts.cf"
hello@example.com|{SHA512-CRYPT}$6$W4rxRQwI6HNMt9n3$riCi5/OqUxnU8eZsOlZwoCnrNgu1gBGPkJc.ER.LhJCu7sOg9i1kBrRIistlBIp938GdBgMlYuoXYUU5A4Qiv0
```
---
**Dovecot "extra fields"**
[Appending a third column will customize "extra fields"][gh-issue::provisioner-file::accounts-extra-fields] when converting account data into a Dovecot UserDB entry.
DMS is not aware of these customizations beyond carrying them over, expect potential for bugs when this feature breaks any assumed conventions used in the scripts (_such as changing the mailbox path or type_).
!!! note
Account creation will normalize the provided email address to lowercase, as DMS does not support multiple case-sensitive address variants.
The email address chosen will also represent the _login username_ credential for mail clients to authenticate with.
### Aliases
!!! info
**Config file:** `docker-data/dms/config/postfix-virtual.cf`
---
The config format is line-based with key value pairs (**alias** --> **target address**), with white-space as a delimiter.
!!! example "`postfix-virtual.cf` config file"
In this example DMS manages mail for the domain `example.com`:
```cf-extra title="postfix-virtual.cf"
# Alias delivers to an existing account:
alias1@example.com hello@example.com
# Alias forwards to an external email address:
alias2@example.com external-account@gmail.com
```
??? warning "Known Issues"
**`setup` CLI prevents an alias and account sharing an address:**
You cannot presently add a new account (`setup email add`) or alias (`setup alias add`) with an address which already exists as an alias or account in DMS.
This [restriction was enforced][gh-issue::bugs::account-alias-overlap] due to [problems it could cause][gh-issue::bugs::account-alias-overlap-problem], although there are [use-cases where you may legitimately require this functionality][gh-issue::feature-request::allow-account-alias-overlap].
For now you must manually edit the `postfix-virtual.cf` file as a workaround. There are no run-time checks outside of the `setup` CLI related to this restriction.
---
**Wildcard catch-all support (`@example.com`):**
While this type of alias without a local-part is supported, you must keep in mind that aliases in Postfix have a higher precedence than a real address associated to a DMS account.
As a result, the wildcard is matched first and will direct mail for that entire domain to the alias target address. To work around this, [you will need an alias for each non-alias address of that domain][gh-issue::bugs::wildcard-catchall].
Additionally, Postfix will read the alias config and choose the alias value that matches the recipient address first. Ensure your more specific aliases for the domain are declared above the wildcard alias in the config file.
---
**Aliasing to another alias or multiple recipients:**
[While aliasing to multiple recipients is possible][gh-discussions::no-support::alias-multiple-targets], DMS does not officially support that.
- You may experience issues when our feature integrations don't expect more than one target per alias.
- These concerns also apply to the usage of nested aliases (_where the recipient target provided is to an alias instead of a real address_). An example is the [incompatibility with `setup alias add`][gh-issue::bugs::alias-nested].
#### Configuring RegEx aliases
!!! info
**Config file:** `docker-data/dms/config/postfix-regexp.cf`
---
This config file is similar to the above `postfix-virtual.cf`, but the alias value is instead configured with a regex pattern.
There is **no `setup` CLI support** for this feature, it is config only.
!!! example "`postfix-regexp.cf` config file"
Deliver all mail for `test` users to `qa@example.com` instead:
```cf-extra title="postfix-regexp.cf"
# Remember to escape regex tokens like `.` => `\.`, otherwise
# your alias pattern may be more permissive than you intended:
/^test[0-9][0-9]*@example\.com/ qa@example.com
```
??? abstract "Technical Details"
`postfix-virtual.cf` has precedence, `postfix-regexp.cf` will only be checked if no alias match was found in `postfix-virtual.cf`.
These files are both copied internally to `/etc/postfix/` and configured in `main.cf` for the `virtual_alias_maps` setting. As `postfix-virtual.cf` is declared first for that setting, it will be processed before using `postfix-regexp.cf` as a fallback.
### Quotas
!!! info
**Config file:** `docker-data/dms/config/dovecot-quotas.cf`
----
The config format is line-based with two fields separated by the delimiter `:`:
- **Dovecot UserDB account:** The user DMS account. It should have a matching field in `postfix-accounts.cf`.
- **Quota limit:** Expressed in bytes (_binary unit suffix is supported: `M` => `MiB`, `G` => `GiB`_).
!!! example "`dovecot-quotas.cf` config file"
For the account with the mailbox address of `hello@example.com`, it may not exceed 5 GiB in storage:
```cf-extra title="dovecot-quotas.cf"
hello@example.com:5G
```
[docs::volumes::config]: ../../advanced/optional-config.md#volumes-config
[gh-issue::provisioner-file::accounts-extra-fields]: https://github.com/docker-mailserver/docker-mailserver/issues/4117
[gh-issue::feature-request::allow-account-alias-overlap]: https://github.com/docker-mailserver/docker-mailserver/issues/3528
[gh-issue::bugs::account-alias-overlap-problem]: https://github.com/docker-mailserver/docker-mailserver/issues/3350#issuecomment-1550528898
[gh-issue::bugs::account-alias-overlap]: https://github.com/docker-mailserver/docker-mailserver/issues/3022#issuecomment-1807816689
[gh-issue::bugs::wildcard-catchall]: https://github.com/docker-mailserver/docker-mailserver/issues/3022#issuecomment-1610452561
[gh-issue::bugs::alias-nested]: https://github.com/docker-mailserver/docker-mailserver/issues/3622#issuecomment-1794504849
[gh-discussions::no-support::alias-multiple-targets]: https://github.com/orgs/docker-mailserver/discussions/3805#discussioncomment-8215417

View File

@ -1,5 +1,5 @@
--- ---
title: 'Advanced | LDAP Authentication' title: 'Account Management | Provisioner (LDAP)'
--- ---
## Introduction ## Introduction
@ -304,5 +304,5 @@ The changes on the configurations necessary to work with Active Directory (**onl
- NET_ADMIN - NET_ADMIN
``` ```
[docs-environment]: ../environment.md [docs-environment]: ../../environment.md
[docs-userpatches]: ./override-defaults/user-patches.md [docs-userpatches]: ../../advanced/override-defaults/user-patches.md

View File

@ -0,0 +1,70 @@
---
title: 'Account Management | Master Accounts (Dovecot)'
hide:
- toc # Hide Table of Contents for this page
---
This feature is useful for administrative tasks like hot backups.
!!! note
This feature is presently [not supported with `ACCOUNT_PROVISIONER=LDAP`][dms::feature::dovecot-master-accounts::caveat-ldap].
!!! info
A _Master Account_:
- Can login as any user (DMS account) and access their mailbox.
- Is not associated to a separate DMS account, nor is it a DMS account itself.
---
**`setup` CLI support**
Use the `setup dovecot-master <add|update|del|list>` commands. These are roughly equivalent to the `setup email` subcommands.
---
**Config file:** `docker-data/dms/config/dovecot-masters.cf`
The config format is the same as [`postfix-accounts.cf` for `ACCOUNT_PROVISIONER=FILE`][docs::account-management::file::accounts].
The only difference is the account field has no `@domain-part` suffix, it is only a username.
??? abstract "Technical Details"
[The _Master Accounts_ feature][dms::feature::dovecot-master-accounts] in DMS configures the [Dovecot Master Users][dovecot-docs::auth::master-users] feature with the Dovecot setting [`auth_master_user_separator`][dovecot-docs::config::auth-master-user-separator] (_where the default value is `*`_).
## Login via Master Account
!!! info
To login as another DMS account (`user@example.com`) with POP3 or IMAP, use the following credentials format:
- Username: `<LOGIN USERNAME>*<MASTER USER>` (`user@example.com*admin`)
- Password: `<MASTER PASSWORD>`
!!! example "Verify login functionality"
In the DMS container, you can verify with the `testsaslauthd` command:
```bash
# Prerequisites:
# A regular DMS account to test login through a Master Account:
setup email add user@example.com secret
# Add a new Master Account:
setup dovecot-master add admin top-secret
```
```bash
# Login with credentials format as described earlier:
testsaslauthd -u 'user@example.com*admin' -p 'top-secret'
```
Alternatively, any mail client should be able to login the equivalent credentials.
[dms::feature::dovecot-master-accounts]: https://github.com/docker-mailserver/docker-mailserver/pull/2535
[dms::feature::dovecot-master-accounts::caveat-ldap]: https://github.com/docker-mailserver/docker-mailserver/pull/2535#issuecomment-1118056745
[dovecot-docs::auth::master-users]: https://doc.dovecot.org/configuration_manual/authentication/master_users/
[dovecot-docs::config::auth-master-user-separator]: https://doc.dovecot.org/settings/core/#core_setting-auth_master_user_separator
[docs::account-management::file::accounts]: ../provisioner/file.md#accounts

View File

@ -0,0 +1,145 @@
---
title: 'Account Management | OAuth2 Support'
hide:
- toc # Hide Table of Contents for this page
---
# Authentication - OAuth2 / OIDC
This feature enables support for delegating DMS account authentication through to an external _Identity Provider_ (IdP).
!!! warning "Receiving mail requires a DMS account to exist"
If you expect DMS to receive mail, you must provision an account into DMS in advance. Otherwise DMS has no awareness of your externally manmaged users and will reject delivery.
There are [plans to implement support to provision users through a SCIM 2.0 API][dms-feature-request::scim-api]. An IdP that can operate as a SCIM Client (eg: Authentik) would then integrate with DMS for user provisioning. Until then you must keep your user accounts in sync manually via your configured [`ACCOUNT_PROVISIONER`][docs::env::account-provisioner].
??? info "How the feature works"
1. A **mail client must have support** to acquire an OAuth2 token from your IdP (_however many clients lack generic OAuth2 / OIDC provider support_).
2. The mail client then provides that token as the user password via the login mechanism `XOAUTH2` or `OAUTHBEARER`.
3. DMS (Dovecot) will then check the validity of that token against the Authentication Service it was configured with.
4. If the response returned is valid for the user account, authentication is successful.
[**XOAUTH2**][google::xoauth2-docs] (_Googles widely adopted implementation_) and **OAUTHBEARER** (_the newer variant standardized by [RFC 7628][rfc::7628] in 2015_) are supported as standards for verifying that a OAuth Bearer Token (_[RFC 6750][rfc::6750] from 2012_) is valid at the identity provider that created the token. The token itself in both cases is expected to be can an opaque _Access Token_, but it is possible to use a JWT _ID Token_ (_which encodes additional information into the token itself_).
A mail client like Thunderbird has limited OAuth2 / OIDC support. The software maintains a hard-coded list of providers supported. Roundcube is a webmail client that does have support for generic providers, allowing you to integrate with a broader range of IdP services.
---
**Documentation for this feature is WIP**
See the [initial feature support][dms-feature::oauth2-pr] and [existing issues][dms-feature::oidc-issues] for guidance that has not yet been documented officially.
??? tip "Verify authentication works"
If you have a compatible mail client you can verify login through that.
---
??? example "CLI - Verify with `curl`"
```bash
# Shell into your DMS container:
docker exec -it dms bash
# Adjust these variables for the methods below to use:
export AUTH_METHOD='OAUTHBEARER' USER_ACCOUNT='hello@example.com' ACCESS_TOKEN='DMS_YWNjZXNzX3Rva2Vu'
# Authenticate via IMAP (Dovecot):
curl --silent --url 'imap://localhost:143' \
--login-options "AUTH=${AUTH_METHOD}" --user "${USER_ACCOUNT}" --oauth2-bearer "${ACCESS_TOKEN}" \
--request 'LOGOUT' \
&& grep "dovecot: imap-login: Login: user=<${USER_ACCOUNT}>, method=${AUTH_METHOD}" /var/log/mail/mail.log
# Authenticate via SMTP (Postfix), sending a mail with the same sender(from) and recipient(to) address:
# NOTE: `curl` seems to require `--upload-file` with some mail content provided to test SMTP auth.
curl --silent --url 'smtp://localhost:587' \
--login-options "AUTH=${AUTH_METHOD}" --user "${USER_ACCOUNT}" --oauth2-bearer "${ACCESS_TOKEN}" \
--mail-from "${USER_ACCOUNT}" --mail-rcpt "${USER_ACCOUNT}" --upload-file - <<< 'RFC 5322 content - not important' \
&& grep "postfix/submission/smtpd.*, sasl_method=${AUTH_METHOD}, sasl_username=${USER_ACCOUNT}" /var/log/mail/mail.log
```
---
**Troubleshooting:**
- Add `--verbose` to the curl options. This will output the protocol exchange which includes if authentication was successful or failed.
- The above example chains the `curl` commands with `grep` on DMS logs (_for Dovecot and Postfix services_). When not running `curl` from the DMS container, ensure you check the logs correctly, or inspect the `--verbose` output instead.
!!! warning "`curl` bug with `XOAUTH2`"
[Older releases of `curl` have a bug with `XOAUTH2` support][gh-issue::curl::xoauth2-bug] since `7.80.0` (Nov 2021) but fixed from `8.6.0` (Jan 2024). It treats `XOAUTH2` as `OAUTHBEARER`.
If you use `docker exec` to run `curl` from within DMS, the current DMS v14 release (_Debian 12 with curl `7.88.1`_) is affected by this bug.
## Config Examples
### Authentik with Roundcube
This example assumes you have already set up:
- A working DMS server
- An Authentik server ([documentation][authentik::docs::install])
- A Roundcube server ([docker image][roundcube::dockerhub-image] or [bare metal install][roundcube::docs::install])
!!! example "Setup Instructions"
=== "1. Docker Mailserver"
Update your Docker Compose ENV config to include:
```env title="compose.yaml"
services:
mailserver:
env:
# Enable the feature:
- ENABLE_OAUTH2=1
# Specify the user info endpoint URL of the oauth2 server for token inspection:
- OAUTH2_INTROSPECTION_URL=https://authentik.example.com/application/o/userinfo/
```
=== "2. Authentik"
1. Create a new OAuth2 provider.
2. Note the client id and client secret. Roundcube will need this.
3. Set the allowed redirect url to the equivalent of `https://roundcube.example.com/index.php/login/oauth` for your RoundCube instance.
=== "3. Roundcube"
Add the following to `oauth2.inc.php` ([documentation][roundcube::docs::config]):
```php
$config['oauth_provider'] = 'generic';
$config['oauth_provider_name'] = 'Authentik';
$config['oauth_client_id'] = '<insert client id here>';
$config['oauth_client_secret'] = '<insert client secret here>';
$config['oauth_auth_uri'] = 'https://authentik.example.com/application/o/authorize/';
$config['oauth_token_uri'] = 'https://authentik.example.com/application/o/token/';
$config['oauth_identity_uri'] = 'https://authentik.example.com/application/o/userinfo/';
// Optional: disable SSL certificate check on HTTP requests to OAuth server. For possible values, see:
// http://docs.guzzlephp.org/en/stable/request-options.html#verify
$config['oauth_verify_peer'] = false;
$config['oauth_scope'] = 'email openid profile';
$config['oauth_identity_fields'] = ['email'];
// Boolean: automatically redirect to OAuth login when opening Roundcube without a valid session
$config['oauth_login_redirect'] = false;
```
[dms-feature::oauth2-pr]: https://github.com/docker-mailserver/docker-mailserver/pull/3480
[dms-feature::oidc-issues]: https://github.com/docker-mailserver/docker-mailserver/issues?q=label%3Afeature%2Fauth-oidc
[docs::env::account-provisioner]: ../../environment.md#account_provisioner
[dms-feature-request::scim-api]: https://github.com/docker-mailserver/docker-mailserver/issues/4090
[google::xoauth2-docs]: https://developers.google.com/gmail/imap/xoauth2-protocol#the_sasl_xoauth2_mechanism
[rfc::6750]: https://datatracker.ietf.org/doc/html/rfc6750
[rfc::7628]: https://datatracker.ietf.org/doc/html/rfc7628
[gh-issue::curl::xoauth2-bug]: https://github.com/curl/curl/issues/10259#issuecomment-1907192556
[authentik::docs::install]: https://goauthentik.io/docs/installation/
[roundcube::dockerhub-image]: https://hub.docker.com/r/roundcube/roundcubemail
[roundcube::docs::install]: https://github.com/roundcube/roundcubemail/wiki/Installation
[roundcube::docs::config]: https://github.com/roundcube/roundcubemail/wiki/Configuration

View File

@ -1,69 +0,0 @@
---
title: 'Advanced | Basic OAuth2 Authentication'
---
## Introduction
!!! warning "This is only a supplement to the existing account provisioners"
Accounts must still be managed via the configured [`ACCOUNT_PROVISIONER`][env::account-provisioner] (FILE or LDAP).
Reasoning for this can be found in [#3480][gh-pr::oauth2]. Future iterations on this feature may allow it to become a full account provisioner.
[gh-pr::oauth2]: https://github.com/docker-mailserver/docker-mailserver/pull/3480
[env::account-provisioner]: ../environment.md#account_provisioner
The present OAuth2 support provides the capability for 3rd-party applications such as Roundcube to authenticate with DMS (dovecot) by using a token obtained from an OAuth2 provider, instead of passing passwords around.
## Example (Authentik & Roundcube)
This example assumes you have:
- A working DMS server set up
- An Authentik server set up ([documentation](https://goauthentik.io/docs/installation/))
- A Roundcube server set up (either [docker](https://hub.docker.com/r/roundcube/roundcubemail/) or [bare metal](https://github.com/roundcube/roundcubemail/wiki/Installation))
!!! example "Setup Instructions"
=== "1. Docker Mailserver"
Edit the following values in `mailserver.env`:
```env
# -----------------------------------------------
# --- OAUTH2 Section ----------------------------
# -----------------------------------------------
# empty => OAUTH2 authentication is disabled
# 1 => OAUTH2 authentication is enabled
ENABLE_OAUTH2=1
# Specify the user info endpoint URL of the oauth2 provider
OAUTH2_INTROSPECTION_URL=https://authentik.example.com/application/o/userinfo/
```
=== "2. Authentik"
1. Create a new OAuth2 provider
2. Note the client id and client secret
3. Set the allowed redirect url to the equivalent of `https://roundcube.example.com/index.php/login/oauth` for your RoundCube instance.
=== "3. Roundcube"
Add the following to `oauth2.inc.php` ([documentation](https://github.com/roundcube/roundcubemail/wiki/Configuration)):
```php
$config['oauth_provider'] = 'generic';
$config['oauth_provider_name'] = 'Authentik';
$config['oauth_client_id'] = '<insert client id here>';
$config['oauth_client_secret'] = '<insert client secret here>';
$config['oauth_auth_uri'] = 'https://authentik.example.com/application/o/authorize/';
$config['oauth_token_uri'] = 'https://authentik.example.com/application/o/token/';
$config['oauth_identity_uri'] = 'https://authentik.example.com/application/o/userinfo/';
// Optional: disable SSL certificate check on HTTP requests to OAuth server. For possible values, see:
// http://docs.guzzlephp.org/en/stable/request-options.html#verify
$config['oauth_verify_peer'] = false;
$config['oauth_scope'] = 'email openid profile';
$config['oauth_identity_fields'] = ['email'];
// Boolean: automatically redirect to OAuth login when opening Roundcube without a valid session
$config['oauth_login_redirect'] = false;
```

View File

@ -1,21 +0,0 @@
---
title: 'Advanced | Dovecot master accounts'
---
## Introduction
A dovecot master account is able to login as any configured user. This is useful for administrative tasks like hot backups.
## Configuration
It is possible to create, update, delete and list dovecot master accounts using `setup.sh`. See `setup.sh help` for usage.
This feature is presently [not supported with LDAP](https://github.com/docker-mailserver/docker-mailserver/pull/2535).
## Logging in
Once a master account is configured, it is possible to connect to any users mailbox using this account. Log in over POP3/IMAP using the following credential scheme:
Username: `<EMAIL ADDRESS>*<MASTER ACCOUNT NAME>`
Password: `<MASTER ACCOUNT PASSWORD>`

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/v14.0.0/target/scripts/helpers/relay.sh [dms-repo::helpers-relay]: https://github.com/docker-mailserver/docker-mailserver/blob/v15.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

@ -10,14 +10,19 @@ environment:
- GETMAIL_POLL=5 - GETMAIL_POLL=5
``` ```
In your DMS config volume (eg: `docker-data/dms/config/`), create a `getmail-<ID>.cf` file for each remote account that you want to retrieve mail and store into a local DMS account. `<ID>` should be replaced by you, and is just the rest of the filename (eg: `getmail-example.cf`). The contents of each file should be configuration like documented below. In your DMS config volume (eg: `docker-data/dms/config/`), add a subdirectory `getmail/` for including your getmail config files (eg: `imap-example.cf`) for each remote account that you want to retrieve mail from and deliver to the mailbox of a DMS account.
The directory structure should similar to this: The content of these config files is documented in the next section with an IMAP and POP3 example to reference.
The directory structure should look similar to this:
```txt ```txt
├── docker-data/dms/config ├── docker-data/dms/config
│   ├── dovecot.cf │   ├── dovecot.cf
│   ├── getmail-example.cf │ ├── getmail
│   │ ├── getmailrc_general.cf
│   │ ├── remote-account1.cf
│   │ ├── remote-account2.cf
│   ├── postfix-accounts.cf │   ├── postfix-accounts.cf
│   └── postfix-virtual.cf │   └── postfix-virtual.cf
├── docker-compose.yml ├── docker-compose.yml
@ -42,7 +47,11 @@ received = false
delivered_to = false delivered_to = false
``` ```
If you want to use a different base config, mount a file to `/etc/getmailrc_general`. This file will replace the default "Common Options" base config above, that all `getmail-<ID>.cf` files will extend with their configs when used. The DMS integration for Getmail generates a `getmailrc` config that prepends the common options of the base config to each remote account config file (`*.cf`) found in the DMS Config Volume `getmail/` directory.
!!! tip "Change the base options"
Add your own base config as `getmail/getmailrc_general.cf` into the DMS Config Volume. It will replace the DMS defaults shown above.
??? example "IMAP Configuration" ??? example "IMAP Configuration"
@ -54,7 +63,7 @@ If you want to use a different base config, mount a file to `/etc/getmailrc_gene
```getmailrc ```getmailrc
[retriever] [retriever]
type = SimpleIMAPRetriever type = SimpleIMAPSSLRetriever
server = imap.gmail.com server = imap.gmail.com
username = alice username = alice
password = notsecure password = notsecure
@ -71,7 +80,7 @@ If you want to use a different base config, mount a file to `/etc/getmailrc_gene
```getmailrc ```getmailrc
[retriever] [retriever]
type = SimplePOP3Retriever type = SimplePOP3SSLRetriever
server = pop3.gmail.com server = pop3.gmail.com
username = alice username = alice
password = notsecure password = notsecure
@ -84,7 +93,7 @@ If you want to use a different base config, mount a file to `/etc/getmailrc_gene
### Polling Interval ### Polling Interval
By default the `getmail` service checks external mail accounts for new mail every 5 minutes. That polling interval is configurable via the `GETMAIL_POLL` ENV variable, with a value in minutes (_default: 5, min: 1, max: 30_): By default the `getmail` service checks external mail accounts for new mail every 5 minutes. That polling interval is configurable via the `GETMAIL_POLL` ENV variable, with a value in minutes (_default: 5, min: 1_):
```yaml ```yaml
environment: environment:
@ -99,3 +108,11 @@ 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

@ -81,31 +81,84 @@ For more examples or a detailed description of the Sieve language have a look at
[sieve-info::examples]: http://sieve.info/examplescripts [sieve-info::examples]: http://sieve.info/examplescripts
[third-party::sieve-examples]: https://support.tigertech.net/sieve#sieve-example-rules-jmp [third-party::sieve-examples]: https://support.tigertech.net/sieve#sieve-example-rules-jmp
## Automatic Sorting Based on Subaddresses ## Automatic Sorting Based on Sub-addresses { #subaddress-mailbox-routing }
It is possible to sort subaddresses such as `user+mailing-lists@example.com` into a corresponding folder (here: `INBOX/Mailing-lists`) automatically. When mail is delivered to your account, it is possible to organize storing mail into folders by the [subaddress (tag)][docs::accounts-subaddressing] used.
```sieve !!! example "Example: `user+<tag>@example.com` to `INBOX/<Tag>`"
This example sorts mail into inbox folders by their tag:
```sieve title="docker-data/dms/config/user@example.com.dovecot.sieve"
require ["envelope", "fileinto", "mailbox", "subaddress", "variables"]; require ["envelope", "fileinto", "mailbox", "subaddress", "variables"];
# Check if the mail recipient address has a tag (:detail)
if envelope :detail :matches "to" "*" { if envelope :detail :matches "to" "*" {
# Create a variable `tag`, with the the captured `to` value normalized (SoCIAL => Social)
set :lower :upperfirst "tag" "${1}"; set :lower :upperfirst "tag" "${1}";
if mailboxexists "INBOX.${1}" {
fileinto "INBOX.${1}"; # Store the mail into a folder with the tag name, nested under your inbox folder:
if mailboxexists "INBOX.${tag}" {
fileinto "INBOX.${tag}";
} else { } else {
fileinto :create "INBOX.${tag}"; fileinto :create "INBOX.${tag}";
} }
} }
``` ```
When receiving mail for `user+social@example.com` it would be delivered into the `INBOX/Social` folder.
??? tip "Only redirect mail for specific tags"
If you want to only handle specific tags, you could replace the envelope condition and tag assignment from the prior example with:
```sieve title="docker-data/dms/config/user@example.com.dovecot.sieve"
# Instead of `:matches`, use the default comparator `:is` (exact match)
if envelope :detail "to" "social" {
set "tag" "Social";
```
```sieve title="docker-data/dms/config/user@example.com.dovecot.sieve"
# Alternatively you can also provide a list of values to match:
if envelope :detail "to" ["azure", "aws"] {
set "tag" "Cloud";
```
```sieve title="docker-data/dms/config/user@example.com.dovecot.sieve"
# Similar to `:matches`, except `:regex` provides enhanced pattern matching.
# NOTE: This example needs you to `require` the "regex" extension
if envelope :detail :regex "to" "^cloud-(azure|aws)$" {
# Normalize the captured azure/aws tag as the resolved value is no longer fixed:
set :lower :upperfirst "vendor" "${1}";
# If a `.` exists in the tag, it will create nested folders:
set "tag" "Cloud.${vendor}";
```
**NOTE:** There is no need to lowercase the tag in the conditional as the [`to` value is a case-insensitive check][sieve-docs::envelope].
??? abstract "Technical Details"
- Dovecot supports this feature via the _Sieve subaddress extension_ ([RFC 5233][rfc::5233::sieve-subaddress]).
- Only a single tag per subaddress is supported. Any additional tag delimiters are part of the tag value itself.
- The Dovecot setting [`recipient_delimiter`][dovecot-docs::config::recipient_delimiter] (default: `+`) configures the tag delimiter. This is where the `local-part` of the recipient address will split at, providing the `:detail` (tag) value for Sieve.
---
`INBOX` is the [default namespace configured by Dovecot][dovecot-docs::namespace].
- If you omit the `INBOX.` prefix from the sieve script above, the mailbox (folder) for that tag is created at the top-level alongside your Trash and Junk folders.
- The `.` between `INBOX` and `${tag}` is important as a [separator to distinguish mailbox names][dovecot-docs::mailbox-names]. This can vary by mailbox format or configuration. DMS uses [`Maildir`][dovecot-docs::mailbox-formats::maildir] by default, which uses `.` as the separator.
- [`lmtp_save_to_detail_mailbox = yes`][dovecot-docs::config::lmtp_save_to_detail_mailbox] can be set in `/etc/dovecot/conf.d/20-lmtp.conf`:
- This implements the feature globally, except for the tag normalization and `INBOX.` prefix parts of the example script.
- However, if the sieve script is also present, the script has precedence and will handle this task instead when the condition is successful, otherwise falling back to the global feature.
## Manage Sieve ## Manage Sieve
The [Manage Sieve](https://doc.dovecot.org/admin_manual/pigeonhole_managesieve_server/) extension allows users to modify their Sieve script by themselves. The authentication mechanisms are the same as for the main dovecot service. ManageSieve runs on port `4190` and needs to be enabled using the `ENABLE_MANAGESIEVE=1` environment variable. The [Manage Sieve](https://doc.dovecot.org/admin_manual/pigeonhole_managesieve_server/) extension allows users to modify their Sieve script by themselves. The authentication mechanisms are the same as for the main dovecot service. ManageSieve runs on port `4190` and needs to be enabled using the `ENABLE_MANAGESIEVE=1` environment variable.
!!! example !!! example
```yaml ```yaml title="compose.yaml"
# compose.yaml
ports: ports:
- "4190:4190" - "4190:4190"
environment: environment:
@ -122,3 +175,14 @@ The extension is known to work with the following ManageSieve clients:
- **[Sieve Editor](https://github.com/thsmi/sieve)** a portable standalone application based on the former Thunderbird plugin. - **[Sieve Editor](https://github.com/thsmi/sieve)** a portable standalone application based on the former Thunderbird plugin.
- **[Kmail](https://kontact.kde.org/components/kmail/)** the mail client of [KDE](https://kde.org/)'s Kontact Suite. - **[Kmail](https://kontact.kde.org/components/kmail/)** the mail client of [KDE](https://kde.org/)'s Kontact Suite.
[docs::accounts-subaddressing]: ../account-management/overview.md#sub-addressing
[dovecot-docs::namespace]: https://doc.dovecot.org/configuration_manual/namespace/
[dovecot-docs::mailbox-names]: https://doc.dovecot.org/configuration_manual/sieve/usage/#mailbox-names
[dovecot-docs::mailbox-formats::maildir]: https://doc.dovecot.org/admin_manual/mailbox_formats/maildir/#maildir-mbox-format
[dovecot-docs::config::lmtp_save_to_detail_mailbox]: https://doc.dovecot.org/settings/core/#core_setting-lmtp_save_to_detail_mailbox
[dovecot-docs::config::recipient_delimiter]: https://doc.dovecot.org/settings/core/#core_setting-recipient_delimiter
[rfc::5233::sieve-subaddress]: https://datatracker.ietf.org/doc/html/rfc5233
[sieve-docs::envelope]: https://thsmi.github.io/sieve-reference/en/test/core/envelope.html

View File

@ -18,10 +18,7 @@ DMS has several locations in the container which may be worth persisting externa
- [Config](#volumes-config): `docker-data/dms/config/` => `/tmp/docker-mailserver/` - [Config](#volumes-config): `docker-data/dms/config/` => `/tmp/docker-mailserver/`
- [Mail Storage](#volumes-mail): `docker-data/dms/mail-data/` => `/var/mail/` - [Mail Storage](#volumes-mail): `docker-data/dms/mail-data/` => `/var/mail/`
- [State](#volumes-state): `docker-data/dms/mail-state/` => `/var/mail-state/` - [State](#volumes-state): `docker-data/dms/mail-state/` => `/var/mail-state/`
- [Logs](#volumes-logs): `docker-data/dms/mail-logs/` => `/var/log/mail/` - [Logs](#volumes-log): `docker-data/dms/mail-logs/` => `/var/log/mail/`
[docker-docs::volumes]: https://docs.docker.com/storage/volumes/
[docker-docs::volumes::bind-mount]: https://docs.docker.com/storage/bind-mounts/
### Mail Storage Volume { #volumes-mail } ### Mail Storage Volume { #volumes-mail }
@ -80,8 +77,8 @@ This is a list of all configuration files and directories which are optional, au
- **postfix-send-access.cf:** List of users denied sending. Modify via [`setup.sh email restrict`][docs-setupsh]. - **postfix-send-access.cf:** List of users denied sending. Modify via [`setup.sh email restrict`][docs-setupsh].
- **postfix-receive-access.cf:** List of users denied receiving. Modify via [`setup.sh email restrict`][docs-setupsh]. - **postfix-receive-access.cf:** List of users denied receiving. Modify via [`setup.sh email restrict`][docs-setupsh].
- **postfix-virtual.cf:** Alias configuration file. Modify via [`setup.sh alias`][docs-setupsh]. - **postfix-virtual.cf:** Alias configuration file. Modify via [`setup.sh alias`][docs-setupsh].
- **postfix-sasl-password.cf:** listing of relayed domains with their respective `<username>:<password>`. Modify via `setup.sh relay add-auth <domain> <username> [<password>]`. (Docs: [Relay-Hosts Auth][docs-relayhosts-senderauth]) - **postfix-sasl-password.cf:** listing of relayed domains with their respective `<username>:<password>`. Modify via `setup.sh relay add-auth <domain> <username> [<password>]`. (Docs: [Relay-Hosts Auth][docs::relay-hosts::advanced])
- **postfix-relaymap.cf:** domain-specific relays and exclusions. Modify via `setup.sh relay add-domain` and `setup.sh relay exclude-domain`. (Docs: [Relay-Hosts Senders][docs-relayhosts-senderhost]) - **postfix-relaymap.cf:** domain-specific relays and exclusions. Modify via `setup.sh relay add-domain` and `setup.sh relay exclude-domain`. (Docs: [Relay-Hosts Senders][docs::relay-hosts::advanced])
- **postfix-regexp.cf:** Regular expression alias file. (Docs: [Aliases][docs-aliases-regex]) - **postfix-regexp.cf:** Regular expression alias file. (Docs: [Aliases][docs-aliases-regex])
- **postfix-regexp-send-only.cf:** Regular expression alias file for sending only. (Docs: [Send-Only Aliases][docs-aliases-send-only]) - **postfix-regexp-send-only.cf:** Regular expression alias file for sending only. (Docs: [Send-Only Aliases][docs-aliases-send-only])
- **ldap-users.cf:** Configuration for the virtual user mapping `virtual_mailbox_maps`. See the [`setup-stack.sh`][github-commit-setup-stack.sh-L411] script. - **ldap-users.cf:** Configuration for the virtual user mapping `virtual_mailbox_maps`. See the [`setup-stack.sh`][github-commit-setup-stack.sh-L411] script.
@ -98,6 +95,9 @@ This is a list of all configuration files and directories which are optional, au
- **user-patches.sh:** this file will be run after all configuration files are set up, but before the postfix, amavis and other daemons are started. (Docs: [FAQ - How to adjust settings with the `user-patches.sh` script][docs-faq-userpatches]) - **user-patches.sh:** this file will be run after all configuration files are set up, but before the postfix, amavis and other daemons are started. (Docs: [FAQ - How to adjust settings with the `user-patches.sh` script][docs-faq-userpatches])
- **rspamd/custom-commands.conf:** list of simple commands to adjust Rspamd modules in an easy way (Docs: [Rspamd][docs-rspamd-commands]) - **rspamd/custom-commands.conf:** list of simple commands to adjust Rspamd modules in an easy way (Docs: [Rspamd][docs-rspamd-commands])
[docker-docs::volumes]: https://docs.docker.com/storage/volumes/
[docker-docs::volumes::bind-mount]: https://docs.docker.com/storage/bind-mounts/
[docs-accounts-quota]: ../../config/user-management.md#quotas [docs-accounts-quota]: ../../config/user-management.md#quotas
[docs-aliases-regex]: ../../config/user-management.md#configuring-regexp-aliases [docs-aliases-regex]: ../../config/user-management.md#configuring-regexp-aliases
[docs-aliases-send-only]: ../../config/user-management.md#send-only-aliases [docs-aliases-send-only]: ../../config/user-management.md#send-only-aliases
@ -107,8 +107,7 @@ This is a list of all configuration files and directories which are optional, au
[docs-faq-userpatches]: ../../faq.md#how-to-adjust-settings-with-the-user-patchessh-script [docs-faq-userpatches]: ../../faq.md#how-to-adjust-settings-with-the-user-patchessh-script
[docs-override-postfix]: ./override-defaults/postfix.md [docs-override-postfix]: ./override-defaults/postfix.md
[docs-override-dovecot]: ./override-defaults/dovecot.md [docs-override-dovecot]: ./override-defaults/dovecot.md
[docs-relayhosts-senderauth]: ./mail-forwarding/relay-hosts.md#sender-dependent-authentication [docs::relay-hosts::advanced]: ./mail-forwarding/relay-hosts.md#advanced-configuration
[docs-relayhosts-senderhost]: ./mail-forwarding/relay-hosts.md#sender-dependent-relay-host
[docs-sieve]: ./mail-sieve.md [docs-sieve]: ./mail-sieve.md
[docs-setupsh]: ../../config/setup.sh.md [docs-setupsh]: ../../config/setup.sh.md
[docs-ssl]: ../../config/security/ssl.md [docs-ssl]: ../../config/security/ssl.md

View File

@ -107,7 +107,7 @@ The `PERMIT_DOCKER` variable in the `mailserver.env` file allows to specify trus
#### Use the slip4netns network driver #### Use the slip4netns network driver
The second workaround is slightly more complicated because the `compose.yaml` has to be modified. The second workaround is slightly more complicated because the `compose.yaml` has to be modified.
As shown in the [fail2ban section](../security/fail2ban.md#podman-with-slirp4netns-port-driver) the `slirp4netns` network driver has to be enabled. As shown in the [fail2ban section][docs::fail2ban::rootless] the `slirp4netns` network driver has to be enabled.
This network driver enables podman to correctly resolve IP addresses but it is not compatible with This network driver enables podman to correctly resolve IP addresses but it is not compatible with
user defined networks which might be a problem depending on your setup. user defined networks which might be a problem depending on your setup.
@ -150,7 +150,7 @@ Remember to run this command as root user.
### Port Forwarding ### Port Forwarding
When it comes to forwarding ports using `firewalld`, see <https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/securing_networks/using-and-configuring-firewalld_securing-networks#port-forwarding_using-and-configuring-firewalld> for more information. When it comes to forwarding ports using `firewalld`, see [these port forwarding docs][firewalld-port-forwarding] for more information.
```bash ```bash
firewall-cmd --permanent --add-forward-port=port=<25|143|465|587|993>:proto=<tcp>:toport=<10025|10143|10465|10587|10993> firewall-cmd --permanent --add-forward-port=port=<25|143|465|587|993>:proto=<tcp>:toport=<10025|10143|10465|10587|10993>
@ -171,5 +171,7 @@ firewall-cmd --reload
Just map all the privilege port with non-privilege port you set in compose.yaml before as root user. Just map all the privilege port with non-privilege port you set in compose.yaml before as root user.
[docs::fail2ban::rootless]: ../security/fail2ban.md#rootless-container
[rootless::podman]: https://github.com/containers/podman/blob/v3.4.1/docs/source/markdown/podman-run.1.md#--networkmode---net [rootless::podman]: https://github.com/containers/podman/blob/v3.4.1/docs/source/markdown/podman-run.1.md#--networkmode---net
[rootless::podman::interface]: https://github.com/containers/podman/blob/v3.4.1/libpod/networking_slirp4netns.go#L264 [rootless::podman::interface]: https://github.com/containers/podman/blob/v3.4.1/libpod/networking_slirp4netns.go#L264
[firewalld-port-forwarding]: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/securing_networks/using-and-configuring-firewalld_securing-networks#port-forwarding_using-and-configuring-firewalld

View File

@ -29,17 +29,32 @@ 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 you're sending domain has a configured DKIM key_) 2. Outbound mail is signed (_when your 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.
??? info "Verification expiry"
Unlike your TLS certificate, your DKIM keypair does not have a fixed expiry associated to it.
Instead, an expiry may be included in your DKIM signature for each mail sent, where a receiver will [refuse to validate the signature for an email after that expiry date][dkim-verification-expiry-refusal]. This is an added precaution to mitigate malicious activity like "DKIM replay attacks", where an already delivered email from a third-party with a trustworthy DKIM signature is leveraged by a spammer when sending mail to an MTA which verifies the DKIM signature successfully, enabling the spammer to bypass spam protections.
Unlike a TLS handshake where you are authenticating trust with future communications, with DKIM once the mail has been received and trust of the signature has been verified, the value of verifying the signature again at a later date is less meaningful since the signature was to ensure no tampering had occurred during delivery through the network.
??? tip "DKIM key rotation"
You can rotate your DKIM keypair by switching to a new DKIM selector (_and DNS updates_), while the previous key and selector remains valid for verification until the last mail signed with that key reaches it's expiry.
DMS does not provide any automation or support for key rotation, [nor is it likely to provide a notable security benefit][gh-discussion::dkim-key-rotation-expiry] to the typical small scale DMS deployment.
### Generating Keys ### Generating Keys
You'll need to repeat this process if you add any new domains. 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-add] - At least one [email account setup][docs-accounts]
- 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"
@ -72,7 +87,7 @@ You should have:
According to [RFC 8301][rfc-8301], keys are preferably between 1024 and 2048 bits. Keys of size 4096-bit or larger may not be compatible to all systems your mail is intended for. According to [RFC 8301][rfc-8301], keys are preferably between 1024 and 2048 bits. Keys of size 4096-bit or larger may not be compatible to all systems your mail is intended for.
You [should not need a key length beyond 2048-bit][github-issue-dkimlength]. If 2048-bit does not meet your security needs, you may want to instead consider adopting key rotation or switching from RSA to ECC keys for DKIM. You [should not need a key length beyond 2048-bit][gh-issue::dkim-length]. If 2048-bit does not meet your security needs, you may want to instead consider adopting key rotation or switching from RSA to ECC keys for DKIM.
??? note "You may need to specify mail domains explicitly" ??? note "You may need to specify mail domains explicitly"
@ -344,7 +359,7 @@ volumes:
- ./docker-data/dms/config/postfix-policyd-spf.conf:/etc/postfix-policyd-spf-python/policyd-spf.conf - ./docker-data/dms/config/postfix-policyd-spf.conf:/etc/postfix-policyd-spf-python/policyd-spf.conf
``` ```
[docs-accounts-add]: ../user-management.md#adding-a-new-account [docs-accounts]: ../account-management/overview.md#accounts
[docs-volumes-config]: ../advanced/optional-config.md#volumes-config [docs-volumes-config]: ../advanced/optional-config.md#volumes-config
[docs-env-opendkim]: ../environment.md#enable_opendkim [docs-env-opendkim]: ../environment.md#enable_opendkim
[docs-env-rspamd]: ../environment.md#enable_rspamd [docs-env-rspamd]: ../environment.md#enable_rspamd
@ -352,7 +367,8 @@ volumes:
[docs-rspamd-config-dropin]: ../security/rspamd.md#manually [docs-rspamd-config-dropin]: ../security/rspamd.md#manually
[cloudflare-dkim-dmarc-spf]: https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/ [cloudflare-dkim-dmarc-spf]: https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/
[rfc-8301]: https://datatracker.ietf.org/doc/html/rfc8301#section-3.2 [rfc-8301]: https://datatracker.ietf.org/doc/html/rfc8301#section-3.2
[github-issue-dkimlength]: https://github.com/docker-mailserver/docker-mailserver/issues/1854#issuecomment-806280929 [gh-discussion::dkim-key-rotation-expiry]: https://github.com/orgs/docker-mailserver/discussions/4068#discussioncomment-9784263
[gh-issue::dkim-length]: https://github.com/docker-mailserver/docker-mailserver/issues/1854#issuecomment-806280929
[rspamd-docs-dkim-checks]: https://www.rspamd.com/doc/modules/dkim.html [rspamd-docs-dkim-checks]: https://www.rspamd.com/doc/modules/dkim.html
[rspamd-docs-dkim-signing]: https://www.rspamd.com/doc/modules/dkim_signing.html [rspamd-docs-dkim-signing]: https://www.rspamd.com/doc/modules/dkim_signing.html
[dns::example-webui]: https://www.vultr.com/docs/introduction-to-vultr-dns/ [dns::example-webui]: https://www.vultr.com/docs/introduction-to-vultr-dns/
@ -360,6 +376,7 @@ volumes:
[dns::wikipedia-zonefile]: https://en.wikipedia.org/wiki/Zone_file [dns::wikipedia-zonefile]: https://en.wikipedia.org/wiki/Zone_file
[dns::webui-dkim]: https://serverfault.com/questions/763815/route-53-doesnt-allow-adding-dkim-keys-because-length-is-too-long [dns::webui-dkim]: https://serverfault.com/questions/763815/route-53-doesnt-allow-adding-dkim-keys-because-length-is-too-long
[dkim-ed25519-support]: https://serverfault.com/questions/1023674/is-ed25519-well-supported-for-the-dkim-validation/1074545#1074545 [dkim-ed25519-support]: https://serverfault.com/questions/1023674/is-ed25519-well-supported-for-the-dkim-validation/1074545#1074545
[dkim-verification-expiry-refusal]: https://mxtoolbox.com/problem/dkim/dkim-signature-expiration
[mxtoolbox-dkim-verifier]: https://mxtoolbox.com/dkim.aspx [mxtoolbox-dkim-verifier]: https://mxtoolbox.com/dkim.aspx
[dmarc-howto-configtags]: https://github.com/internetstandards/toolbox-wiki/blob/master/DMARC-how-to.md#overview-of-dmarc-configuration-tags [dmarc-howto-configtags]: https://github.com/internetstandards/toolbox-wiki/blob/master/DMARC-how-to.md#overview-of-dmarc-configuration-tags
[dmarc-tool-gca]: https://dmarcguide.globalcyberalliance.org [dmarc-tool-gca]: https://dmarcguide.globalcyberalliance.org

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-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::fail2ban::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
@ -123,7 +123,7 @@ This could be from outdated software, or running a system that isn't able to pro
[docs::faq-bare-domain]: ../faq.md#can-i-use-a-nakedbare-domain-ie-no-hostname [docs::faq-bare-domain]: ../faq.md#can-i-use-a-nakedbare-domain-ie-no-hostname
[docs-ipv6]: ./advanced/ipv6.md [docs-ipv6]: ./advanced/ipv6.md
[docs-introduction]: ../introduction.md [docs-introduction]: ../introduction.md
[docs-rootless-portdriver]: ./security/fail2ban.md#running-inside-a-rootless-container [docs::fail2ban::rootless-portdriver]: ./security/fail2ban.md#rootless-container
[docs-usage]: ../usage.md [docs-usage]: ../usage.md
[gh-issues]: https://github.com/docker-mailserver/docker-mailserver/issues [gh-issues]: https://github.com/docker-mailserver/docker-mailserver/issues

View File

@ -39,6 +39,12 @@ Default: 5000
The User ID assigned to the static vmail user for `/var/mail` (_Mail storage managed by Dovecot_). The User ID assigned to the static vmail user for `/var/mail` (_Mail storage managed by Dovecot_).
!!! warning "Incompatible UID values"
- A value of [`0` (root) is not compatible][gh-issue::vmail-uid-cannot-be-root].
- This feature will attempt to adjust the `uid` for the `docker` user (`/etc/passwd`), hence the error emitted to logs if the UID is already assigned to another user.
- The feature appears to work with other UID values that are already assigned in `/etc/passwd`, even though Dovecot by default has a setting for the minimum UID as `500`.
##### DMS_VMAIL_GID ##### DMS_VMAIL_GID
Default: 5000 Default: 5000
@ -47,24 +53,12 @@ The Group ID assigned to the static vmail group for `/var/mail` (_Mail storage m
##### ACCOUNT_PROVISIONER ##### ACCOUNT_PROVISIONER
Configures the provisioning source of user accounts (including aliases) for user queries and authentication by services managed by DMS (_Postfix and Dovecot_). Configures the [provisioning source of user accounts][docs::account-management::overview] (including aliases) for user queries and authentication by services managed by DMS (_Postfix and Dovecot_).
!!! tip "OAuth2 Support" - **FILE** => use local files
Presently DMS supports OAuth2 only as an supplementary authentication method.
- A third-party service must provide a valid token for the user which Dovecot validates with the authentication service provider. To enable this feature reference the [OAuth2 configuration example guide][docs::auth::oauth2-config-guide].
- User accounts must be provisioned to receive mail via one of the supported `ACCOUNT_PROVISIONER` providers.
- User provisioning via OIDC is planned for the future, see [this tracking issue](https://github.com/docker-mailserver/docker-mailserver/issues/2713).
[docs::auth::oauth2-config-guide]: ./advanced/auth-oauth2.md
- **empty** => use FILE
- LDAP => use LDAP authentication - LDAP => use LDAP authentication
- OIDC => use OIDC authentication (**not yet implemented**)
- FILE => use local files (this is used as the default)
A second container for the ldap service is necessary (e.g. [`bitnami/openldap`](https://hub.docker.com/r/bitnami/openldap/)). LDAP requires an external service (e.g. [`bitnami/openldap`](https://hub.docker.com/r/bitnami/openldap/)).
##### PERMIT_DOCKER ##### PERMIT_DOCKER
@ -208,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 his own or his alias addresses. Addresses with [extension delimiters](http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages. - 1 => Mail spoofing denied. Each user may only send with their own or their alias addresses. Addresses with [extension delimiters](http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages.
To allow certain accounts to send as other addresses, set the `SPOOF_PROTECTION` to `1` and see [the Aliases page in the documentation][docs-aliases]. To allow certain accounts to send as other addresses, set the `SPOOF_PROTECTION` to `1` and see [the Aliases page in the documentation][docs-aliases].
@ -303,7 +297,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://wiki2.dovecot.org/MailboxFormat). 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).
##### POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME ##### POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME
@ -740,7 +734,7 @@ Enable or disable `getmail`.
##### GETMAIL_POLL ##### GETMAIL_POLL
- **5** => `getmail` The number of minutes for the interval. Min: 1; Max: 30; Default: 5. - **5** => `getmail` The number of minutes for the interval. Min: 1; Default: 5.
#### OAUTH2 #### OAUTH2
@ -918,22 +912,26 @@ Note: This postgrey setting needs `ENABLE_POSTGREY=1`
##### SASLAUTHD_MECHANISMS ##### SASLAUTHD_MECHANISMS
- **empty** => pam DMS only implements support for these mechanisms:
- `ldap` => authenticate against ldap server
- `shadow` => authenticate against local user db - **`ldap`** => Authenticate against an LDAP server
- `mysql` => authenticate against mysql db - `rimap` => Authenticate against an IMAP server
- `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** => same as `LDAP_SERVER_HOST` - **empty** => Use the same value 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
@ -1137,13 +1135,14 @@ Provide the credentials to use with `RELAY_HOST` or `DEFAULT_RELAY_HOST`.
- Add the exact relayhost value (`host:port` / `[host]:port`) from the generated `/etc/postfix/relayhost_map`, or `main.cf:relayhost` (`DEFAULT_RELAY_HOST`). - Add the exact relayhost value (`host:port` / `[host]:port`) from the generated `/etc/postfix/relayhost_map`, or `main.cf:relayhost` (`DEFAULT_RELAY_HOST`).
- `setup relay ...` is missing support, you must instead add these manually to `postfix-sasl-password.cf`. - `setup relay ...` is missing support, you must instead add these manually to `postfix-sasl-password.cf`.
[gh-issue::vmail-uid-cannot-be-root]: https://github.com/docker-mailserver/docker-mailserver/issues/4098#issuecomment-2257201025
[docs-rspamd]: ./security/rspamd.md [docs-rspamd]: ./security/rspamd.md
[docs-tls]: ./security/ssl.md [docs-tls]: ./security/ssl.md
[docs-tls-letsencrypt]: ./security/ssl.md#lets-encrypt-recommended [docs-tls-letsencrypt]: ./security/ssl.md#lets-encrypt-recommended
[docs-tls-manual]: ./security/ssl.md#bring-your-own-certificates [docs-tls-manual]: ./security/ssl.md#bring-your-own-certificates
[docs-tls-selfsigned]: ./security/ssl.md#self-signed-certificates [docs-tls-selfsigned]: ./security/ssl.md#self-signed-certificates
[docs-accounts-quota]: ./user-management.md#quotas [docs-accounts-quota]: ./user-management.md#quotas
[docs-aliases]: ./user-management.md#send-only-aliases
[docs::relay-host]: ./advanced/mail-forwarding/relay-hosts.md [docs::relay-host]: ./advanced/mail-forwarding/relay-hosts.md
[docs::dms-volumes-state]: ./advanced/optional-config.md#volumes-state [docs::dms-volumes-state]: ./advanced/optional-config.md#volumes-state
[postfix-config::relayhost]: https://www.postfix.org/postconf.5.html#relayhost [postfix-config::relayhost]: https://www.postfix.org/postconf.5.html#relayhost

View File

@ -14,18 +14,48 @@ hide:
## Configuration ## Configuration
!!! warning Enabling Fail2Ban support can be done via ENV, but also requires granting at least the `NET_ADMIN` capability to interact with the kernel and ban IP addresses.
DMS must be launched with the `NET_ADMIN` capability in order to be able to install the NFTables rules that actually ban IP addresses. Thus, either include `--cap-add=NET_ADMIN` in the `docker run` command, or the equivalent in the `compose.yaml`: !!! example
```yaml === "Docker Compose"
```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, not IPTables (legacy). We have observed that older systems, for example NAS systems, do not support the modern NFTables rules. You will need to configure F2B to use legacy IPTables again, for example with the [``fail2ban-jail.cf``][github-file-f2bjail], see the [section on configuration further down below](#custom-files). DMS configures F2B to use [NFTables][network::nftables], not [IPTables (legacy)][network::iptables-legacy].
We have observed that older systems (for example NAS systems), do not support the modern NFTables rules. You will need to configure F2B to use legacy IPTables again, for example with the [`fail2ban-jail.cf`][github-file-f2bjail], see the [section on configuration further down below](#custom-files).
[security::cap-net-admin]: https://0xn3va.gitbook.io/cheat-sheets/container/escaping/excessive-capabilities#cap_net_admin
[security::cap-net-raw]: https://0xn3va.gitbook.io/cheat-sheets/container/escaping/excessive-capabilities#cap_net_raw
[lsio:f2b-image]: https://docs.linuxserver.io/images/docker-fail2ban
[network::nftables]: https://en.wikipedia.org/wiki/Nftables
[network::iptables-legacy]: https://developers.redhat.com/blog/2020/08/18/iptables-the-two-variants-and-their-relationship-with-nftables#two_variants_of_the_iptables_command
### DMS Defaults ### DMS Defaults
@ -78,7 +108,7 @@ docker exec <CONTAINER NAME> setup fail2ban [<ban|unban> <IP>]
docker exec <CONTAINER NAME> setup fail2ban log docker exec <CONTAINER NAME> setup fail2ban log
``` ```
## Running Inside A Rootless Container ## Running Inside A Rootless Container { #rootless-container }
[`RootlessKit`][rootless::rootless-kit] is the _fakeroot_ implementation for supporting _rootless mode_ in Docker and Podman. By default, RootlessKit uses the [`builtin` port forwarding driver][rootless::port-drivers], which does not propagate source IP addresses. [`RootlessKit`][rootless::rootless-kit] is the _fakeroot_ implementation for supporting _rootless mode_ in Docker and Podman. By default, RootlessKit uses the [`builtin` port forwarding driver][rootless::port-drivers], which does not propagate source IP addresses.

View File

@ -6,9 +6,41 @@ 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 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. 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.
## Related Environment Variables ### Enable Rspamd
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:
@ -23,17 +55,11 @@ The following environment variables are related to Rspamd:
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)
With these variables, you can enable Rspamd itself, and you can enable / disable certain features related to Rspamd. ## Overview of Rspamd support
## 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
!!! tip "Attention" !!! note "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).
@ -79,13 +105,16 @@ 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 restarts, and provide a way to keep backup. - With the volume mount, the snapshot will restore the Redis data across container updates, and provide a way to keep a 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.
@ -96,7 +125,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"
@ -108,9 +137,13 @@ 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::basic-options] 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::config::global] yourself. Make sure to also read our [FAQ page on DNS servers][docs::faq::dns-servers].
!!! warning !!! warning
@ -142,7 +175,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"
@ -152,106 +185,141 @@ The [RBL module][rspamd-docs::modules::rbl] is enabled by default. As a conseque
## Providing Custom Settings & Overriding Settings ## Providing Custom Settings & Overriding Settings
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). !!! info "Rspamd config overriding precedence"
### Manually Rspamd has a layered approach for configuration with [`local.d` and `override.d` config directories][rspamd-docs::config-directories].
!!! question "What is [`docker-data/dms/config/`][docs::dms-volumes-config]?" - DMS [extends the Rspamd default configs via `/etc/rspamd/local.d/`][dms-repo::default-rspamd-configuration].
- 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/`.
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. !!! abstract "Reference docs for Rspamd config"
!!! question "What is the [`local.d` directory and how does it compare to `override.d`][rspamd-docs::config-directories]?" - [Config Overview][rspamd-docs::config::overview], [Quickstart guide][rspamd-docs::config::quickstart], and [Config Syntax (UCL)][rspamd-docs::config::ucl-syntax]
- 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_)
!!! warning "Clashing Overrides" !!! tip "View rendered config"
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. `rspamadm configdump` will output the full rspamd configuration that is used should you need it for troubleshooting / inspection.
### With the Help of a Custom File - You can also see which modules are enabled / disabled via `rspamadm configdump --modules-state`
- 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.
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: ### Using `custom-commands.conf` { #with-the-help-of-a-custom-file }
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
COMMAND ARGUMENT1 ARGUMENT2 ARGUMENT3 # For /etc/rspamd/override.d/{options.inc,worker-controller.inc,worker-proxy}.inc
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>
``` ```
where `COMMAND` can be: **Syntax:**
1. `disable-module`: disables the module with name `ARGUMENT1` - Blank lines are ok.
2. `enable-module`: explicitly enables the module with name `ARGUMENT1` - `#` at the start of a line represents a comment for adding notes.
3. `set-option-for-module`: sets the value for option `ARGUMENT2` to `ARGUMENT3` inside module `ARGUMENT1` - `<OPTION VALUE>` and `<CONTENT>` will contain the remaining content of their line, any preceding inputs are delimited by white-space.
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 "File Names & Extensions" ??? note "`<MODULE NAME>` can also target non-module configs"
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! 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.
You can also have comments (the line starts with `#`) and blank lines in `custom-commands.conf` - they are properly handled and not evaluated. ??? example
!!! tip "Adjusting Modules This Way" ```conf title="rspamd/custom-commands.conf"
# If you're confident you've properly secured access to the rspamd web service/API (Default port: 11334)
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)! # with your own auth layer (eg: reverse-proxy) you can bypass rspamd requiring credentials:
# 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"
# See 2. # Some settings aren't documented well, you may find them in snippets or Rspamds default config files:
# 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
# See 3. # Disable the `chartable` module:
# 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](https://app.abusix.com/signup) 1. [Create an account][abusix-web::register]
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
@ -267,17 +335,28 @@ 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::basic-options]: https://rspamd.com/doc/configuration/options.html [rspamd-docs::config::ucl-syntax]: https://rspamd.com/doc/configuration/ucl.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
[www::rbl-vs-dnsbl]: https://forum.eset.com/topic/25277-dnsbl-vs-rbl-mail-security/?do=findComment&comment=119818 [wikipedia::arc]: https://en.wikipedia.org/wiki/Authenticated_Received_Chain
[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/blob/v14.0.0/target/rspamd/local.d/actions.conf [dms-repo::rspamd-actions-config]: https://github.com/docker-mailserver/docker-mailserver/tree/v15.0.0/target/rspamd/local.d/actions.conf
[dms-repo::default-rspamd-configuration]: https://github.com/docker-mailserver/docker-mailserver/tree/v14.0.0/target/rspamd [dms-repo::default-rspamd-configuration]: https://github.com/docker-mailserver/docker-mailserver/tree/v15.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

@ -6,7 +6,7 @@ There are multiple options to enable SSL (via [`SSL_TYPE`][docs-env::ssl-type]):
- Using [letsencrypt](#lets-encrypt-recommended) (recommended) - Using [letsencrypt](#lets-encrypt-recommended) (recommended)
- Using [Caddy](#caddy) - Using [Caddy](#caddy)
- Using [Traefik](#traefik-v2) - Using [Traefik](#traefik)
- Using [self-signed certificates](#self-signed-certificates) - Using [self-signed certificates](#self-signed-certificates)
- Using [your own certificates](#bring-your-own-certificates) - Using [your own certificates](#bring-your-own-certificates)
@ -240,7 +240,7 @@ After completing the steps above, your certificate should be ready to use.
image: certbot/dns-cloudflare:latest image: certbot/dns-cloudflare:latest
command: renew --dns-cloudflare --dns-cloudflare-credentials /run/secrets/cloudflare-api-token command: renew --dns-cloudflare --dns-cloudflare-credentials /run/secrets/cloudflare-api-token
volumes: volumes:
- ./docker-data/certbot/certs/:/etc/letsencrtypt/ - ./docker-data/certbot/certs/:/etc/letsencrypt/
- ./docker-data/certbot/logs/:/var/log/letsencrypt/ - ./docker-data/certbot/logs/:/var/log/letsencrypt/
secrets: secrets:
- cloudflare-api-token - cloudflare-api-token
@ -485,6 +485,8 @@ 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:
@ -510,9 +512,12 @@ 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 {
tls internal { # Optionally provision RSA 2048-bit certificate instead of ECDSA P-256:
tls {
key_type rsa2048 key_type rsa2048
} }
@ -522,10 +527,12 @@ DSM-generated letsencrypt certificates get auto-renewed every three months.
} }
``` ```
While DMS does not need a webserver to work, this workaround will provision a TLS certificate for DMS to use. !!! info
- [`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. An explicit `tls` directive affects only the site-address block it's used in:
- [`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`"
@ -558,19 +565,36 @@ 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
# Add a dummy directive is required: # Adding 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}}"
``` ```
!!! warning "Caddy certificate location varies" !!! warning "Caddy certificate location varies"
The path contains the certificate provisioner used. This path may be different from the example above for you and may change over time when multiple provisioner services are used][dms-pr-feedback::caddy-provisioning-gotcha]. The path contains the certificate provisioner used. This path may be different from the example above for you and may change over time when [multiple ACME provisioner services are used][dms-pr-feedback::caddy-provisioning-gotcha].
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.
@ -583,7 +607,7 @@ This setup only comes with one caveat - The domain has to be configured on anoth
???+ example "Example Code" ???+ example "Example Code"
Here is an example setup for [`docker-compose`](https://docs.docker.com/compose/): Here is an example setup for [`Docker Compose`](https://docs.docker.com/compose/):
```yaml ```yaml
services: services:

View File

@ -145,7 +145,7 @@ Unlike with HTTP where a web browser client communicates directly with the serve
Other machines that facilitate a connection that generally aren't taken into account can exist between a client and server, such as those where your connection passes through your ISP provider are capable of compromising a `cleartext` connection through interception. Other machines that facilitate a connection that generally aren't taken into account can exist between a client and server, such as those where your connection passes through your ISP provider are capable of compromising a `cleartext` connection through interception.
[docs-accounts]: ../user-management.md#accounts [docs-accounts]: ../account-management/overview.md#accounts
[docs-relays]: ../advanced/mail-forwarding/relay-hosts.md [docs-relays]: ../advanced/mail-forwarding/relay-hosts.md
[iana-services-465]: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=465 [iana-services-465]: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=465
[starttls-policy-list]: https://github.com/EFForg/starttls-everywhere#email-security-database-starttls-policy-list [starttls-policy-list]: https://github.com/EFForg/starttls-everywhere#email-security-database-starttls-policy-list

View File

@ -1,100 +0,0 @@
# User Management
## Accounts
Users (email accounts) are managed in `/tmp/docker-mailserver/postfix-accounts.cf`. The best way to manage accounts is to use the reliable `setup` command inside the container. Just run `docker exec <CONTAINER NAME> setup help` and have a look at the section about subcommands, specifically the `email` subcommand.
### Adding a new Account
#### Via `setup` inside the container
You can add an account by running `docker exec -ti <CONTAINER NAME> setup email add <NEW ADDRESS>`. This method is strongly preferred.
#### Manually
!!! warning
This method is discouraged!
Alternatively, you may directly add the full email address and its encrypted password, separated by a pipe. To generate a new mail account data, directly from your host, you could for example run the following:
```sh
docker run --rm -it \
--env MAIL_USER=user1@example.com \
--env MAIL_PASS=mypassword \
ghcr.io/docker-mailserver/docker-mailserver:latest \
/bin/bash -c \
'echo "${MAIL_USER}|$(doveadm pw -s SHA512-CRYPT -u ${MAIL_USER} -p ${MAIL_PASS})" >>docker-data/dms/config/postfix-accounts.cf'
```
You will then be asked for a password, and be given back the data for a new account entry, as text. To actually _add_ this new account, just copy all the output text in `docker-data/dms/config/postfix-accounts.cf` file of your running container.
The result could look like this:
```cf
user1@example.com|{SHA512-CRYPT}$6$2YpW1nYtPBs2yLYS$z.5PGH1OEzsHHNhl3gJrc3D.YMZkvKw/vp.r5WIiwya6z7P/CQ9GDEJDr2G2V0cAfjDFeAQPUoopsuWPXLk3u1
```
### Quotas
- `imap-quota` is enabled and allow clients to query their mailbox usage.
- When the mailbox is deleted, the quota directive is deleted as well.
- Dovecot quotas support LDAP, **but it's not implemented** (_PRs are welcome!_).
## Aliases
The best way to manage aliases is to use the reliable `setup` script inside the container. Just run `docker exec <CONTAINER NAME> setup help` and have a look at the section about subcommands, specifically the `alias`-subcommand.
### About
You may read [Postfix's documentation on virtual aliases][postfix-docs-alias] first. Aliases are managed in `/tmp/docker-mailserver/postfix-virtual.cf`. An alias is a full email address that will either be:
- delivered to an existing account registered in `/tmp/docker-mailserver/postfix-accounts.cf`
- redirected to one or more other email addresses
Alias and target are space separated. An example on a server with `example.com` as its domain:
```cf
# Alias delivered to an existing account
alias1@example.com user1@example.com
# Alias forwarded to an external email address
alias2@example.com external-account@gmail.com
```
### Configuring RegExp Aliases
Additional regexp aliases can be configured by placing them into `docker-data/dms/config/postfix-regexp.cf`. The regexp aliases get evaluated after the virtual aliases (container path: `/tmp/docker-mailserver/postfix-virtual.cf`). For example, the following `docker-data/dms/config/postfix-regexp.cf` causes all email sent to "test" users to be delivered to `qa@example.com` instead:
```cf
/^test[0-9][0-9]*@example.com/ qa@example.com
```
### Address Tags (Extension Delimiters) as an alternative to Aliases
Postfix supports so-called address tags, in the form of plus (+) tags - i.e. `address+tag@example.com` will end up at `address@example.com`. This is configured by default and the (configurable!) separator is set to `+`. For more info, see [Postfix's official documentation][postfix-docs-extension-delimiters].
!!! note
If you do decide to change the configurable separator, you must add the same line to *both* `docker-data/dms/config/postfix-main.cf` and `docker-data/dms/config/dovecot.cf`, because Dovecot is acting as the delivery agent. For example, to switch to `-`, add:
```cf
recipient_delimiter = -
```
### Send-Only Aliases
Sometimes, it may be useful to allow certain accounts to send as other accounts, even when [the `SPOOF_PROTECTION` environment variable is enabled][spoof-protection]. This may be used to allow services to send accounts as other addresses/users without needing to disable spoof protection entirely, and without affecting incoming mail (which traditional [aliases](#aliases) would do).
To configure these aliases, add them to `docker-data/dms/config/postfix-regexp-send-only.cf` in the same format as [the other regexp aliases](#configuring-regexp-aliases). For example:
```cf
/^.*@example.com$/ admin@example.com
/^.*$/ superadmin@example.com
```
In this example, `admin@example.com` would be able to send as any address at `example.com` and `superadmin@example.com` would be able to send as any address at any domain.
[spoof-protection]: ./environment.md#spoof_protection
[postfix-docs-alias]: http://www.postfix.org/VIRTUAL_README.html#virtual_alias
[postfix-docs-extension-delimiters]: http://www.postfix.org/postconf.5.html#recipient_delimiter

View File

@ -15,13 +15,20 @@ 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. Navigate into the `docs/` directory. Then run: Alternatively you can make the changes locally. For that you'll need to have Docker installed and run:
```sh ```sh
docker run --rm -it -p 8000:8000 -v "${PWD}:/docs" squidfunk/mkdocs-material # From the root directory of the git clone:
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

@ -45,15 +45,15 @@ The development workflow is the following:
1. Fork the project and clone your fork with `git clone --recurse-submodules ...` or run `git submodule update --init --recursive` after you cloned your fork 1. Fork the project and clone your fork with `git clone --recurse-submodules ...` or run `git submodule update --init --recursive` after you cloned your fork
2. Write the code that is needed :D 2. Write the code that is needed :D
3. Add integration tests if necessary 3. Add integration tests if necessary
4. [Prepare your environment and run linting and tests][docs-general-tests] 4. [Prepare your environment and run linting and tests][docs::contributing::tests]
5. Document your improvements if necessary (e.g. if you introduced new environment variables, describe those in the [ENV documentation][docs-environment]) and add your changes the changelog under the "Unreleased" section 5. Document your improvements if necessary (e.g. if you introduced new environment variables, describe those in the [ENV documentation][docs::env]) and add your changes the changelog under the "Unreleased" section
6. [Commit][commit] (and [sign your commit][gpg]), push and create a pull-request to merge into `master`. Please **use the pull-request template** to provide a minimum of contextual information and make sure to meet the requirements of the checklist. 6. [Commit][commit] (and [sign your commit][gpg]), push and create a pull-request to merge into `master`. Please **use the pull-request template** to provide a minimum of contextual information and make sure to meet the requirements of the checklist.
Pull requests are automatically tested against the CI and will be reviewed when tests pass. When your changes are validated, your branch is merged. CI builds the new `:edge` image immediately and your changes will be includes in the next version release. Pull requests are automatically tested against the CI and will be reviewed when tests pass. When your changes are validated, your branch is merged. CI builds the new `:edge` image immediately and your changes will be includes in the next version release.
[docs-latest]: https://docker-mailserver.github.io/docker-mailserver/latest [docs-latest]: https://docker-mailserver.github.io/docker-mailserver/latest
[github-file-readme]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md [github-file-readme]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md
[docs-environment]: ../config/environment.md [docs::env]: ../config/environment.md
[docs-general-tests]: ./general.md#tests [docs::contributing::tests]: ./tests.md
[commit]: https://help.github.com/articles/closing-issues-via-commit-messages/ [commit]: https://help.github.com/articles/closing-issues-via-commit-messages/
[gpg]: https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key [gpg]: https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key

View File

@ -32,7 +32,7 @@ We make use of build features that require a recent version of Docker. v23.0 or
The `Dockerfile` includes several build [`ARG`][docker-docs::builder-arg] instructions that can be configured: The `Dockerfile` includes several build [`ARG`][docker-docs::builder-arg] instructions that can be configured:
- `DOVECOT_COMMUNITY_REPO`: Install Dovecot from the community repo instead of from Debian (default = 1) - `DOVECOT_COMMUNITY_REPO`: Install Dovecot from the community repo instead of from Debian (default = 0)
- `DMS_RELEASE`: The image version (default = edge) - `DMS_RELEASE`: The image version (default = edge)
- `VCS_REVISION`: The git commit hash used for the build (default = unknown) - `VCS_REVISION`: The git commit hash used for the build (default = unknown)

View File

@ -0,0 +1,178 @@
# Dovecot Full Text Search (FTS) using the Solr Backend
Dovecot supports several FTS backends for providing fast and efficient full text searching of e-mails directly from the IMAP server.
As the size of your mail storage grows, the benefits of FTS are especially notable:
- Without FTS, Dovecot would perform a search query by checking each individual email stored for a match, and then repeat this process again from scratch for the exact same query in future.
- Some mail clients (_like Thunderbird_) may provide their own indexing and search features when all mail to search is stored locally, otherwise Dovecot needs to handle the search query (_for example webmail and mobile clients, like Gmail_).
- FTS indexes each mail into a database for querying instead, where it can skip the cost of inspecting irrelevant emails for a query.
!!! warning "This is a community contributed guide"
It extends [our official docs for Dovecot FTS][docs::dovecot::full-text-search] with a focus on Apache Solr. DMS does not officially support this integration.
## Setup Solr for DMS
An FTS backend supported by Dovecot is [Apache Solr][github-solr], a fast and efficient multi-purpose search indexer.
### Add the required `dovecot-solr` package
As the official DMS image does not provide `dovecot-solr`, you'll need to include the package in your own image (_extending a DMS release as a base image_), or via our [`user-patches.sh` feature][docs::user-patches]:
<!-- This empty quote block is purely for a visual border -->
!!! quote ""
=== "`user-patches.sh`"
If you'd prefer to avoid a custom image build. This approach is simpler but with the caveat that any time the container is restarted, you'll have a delay as the package is installed each time.
```bash
#!/bin/bash
apt-get update && apt-get install dovecot-solr
```
=== "`compose.yaml`"
A custom DMS image does not add much friction. You do not need a separate `Dockerfile` as Docker Compose supports building from an inline `Dockerfile` in your `compose.yaml`.
The `image` key of the service is swapped for the `build` key instead, as shown below:
```yaml
services:
mailserver:
hostname: mail.example.com
# The `image` setting now represents the tag for the local build configured below:
image: local/dms:${DMS_TAG?Must set DMS image tag}
# Local build (no need to try pull `image` remotely):
pull_policy: build
# Add this `build` section to your real `compose.yaml` for your DMS service:
build:
dockerfile_inline: |
FROM docker.io/mailserver/docker-mailserver:${DMS_TAG?Must set DMS image tag}
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 `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.
- 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`?"
This integration is not officially supported in DMS as no maintainer is able to provide troubleshooting support.
Prior to v14, the package was included but the community contributed guide had been outdated for several years that it was non-functional. It was decided that it was better to drop support and docs, however some DMS users voiced active use of Solr and it's benefits over Xapian for FTS which led to these revised docs.
**ARM64 builds do not have support for `dovecot-solr`**. Additionally the [user demand for including `dovecot-solr` is presently too low][gh-dms::feature-request::dovecot-solr-package] to justify vs the minimal effort to add additional packages as shown above.
### `compose.yaml` config
Firstly you need a working Solr container, for this the [official docker image][dockerhub-solr] will do:
```yaml
services:
solr:
image: solr:latest
container_name: dms-solr
environment:
# As Solr can be quite resource hungry, raise the memory limit to 2GB.
# The default is 512MB, which may be exhausted quickly.
SOLR_JAVA_MEM: "-Xms2g -Xmx2g"
volumes:
- ./docker-data/solr:/var/solr
restart: always
```
DMS will connect internally to the `solr` service above. Either have both services in the same `compose.yaml` file, or ensure that the containers are connected to the same docker network.
### Configure Solr for Dovecot
1. Once the Solr container is started, you need to configure a "Solr core" for Dovecot:
```bash
docker exec -it dms-solr /bin/sh
solr create -c dovecot
cp -R /opt/solr/contrib/analysis-extras/lib /var/solr/data/dovecot
```
Stop the `dms-solr` container and you should now have a `./data/dovecot` folder in the local bind mount volume.
2. Solr needs a schema that is specifically tailored for Dovecot FTS.
As of writing of this guide, Solr 9 is the current release. [Dovecot provides the required schema configs][github-dovecot::core-docs] for Solr, copy the following two v9 config files to `./data/dovecot` and rename them accordingly:
- `solr-config-9.xml` (_rename to `solrconfig.xml`_)
- `solr-schema-9.xml` (_rename to `schema.xml`_)
Additionally, remove the `managed-schema.xml` file from `./data/dovecot` and ensure the two files you copied have a [UID and GID of `8983`][dockerfile-solr-uidgid] assigned.
Start the Solr container once again, you should now have a working Solr core specifically for Dovecot FTS.
3. Configure Dovecot in DMS to connect to this Solr core:
Create a `10-plugin.conf` file in your `./config/dovecot` folder with this contents:
```config
mail_plugins = $mail_plugins fts fts_solr
plugin {
fts = solr
fts_autoindex = yes
fts_solr = url=http://dms-solr:8983/solr/dovecot/
}
```
Add a volume mount for that config to your DMS service in `compose.yaml`:
```yaml
services:
mailserver:
volumes:
- ./docker-data/config/dovecot/10-plugin.conf:/etc/dovecot/conf.d/10-plugin.conf:ro
```
### Trigger Dovecot FTS indexing
After following the previous steps, restart DMS and run this command to have Dovecot re-index all mail:
```bash
docker compose exec mailserver doveadm fts rescan -A
```
!!! info "Indexing will take a while depending on how large your mail folders"
Usually within 15 minutes or so, you should be able to search your mail using the Dovecot FTS feature! :tada:
### Compatibility
Since Solr 9.8.0 was released (Jan 2025), a breaking change [deprecates support for `<lib>` directives][solr::9.8::lib-directive] which is presently used by the Dovecot supplied Solr config (`solr-config-9.xml`) to automatically load additional jars required.
To enable support for `<lib>` directives, add the following ENV to your `solr` container:
```yaml
services:
solr:
environment:
SOLR_CONFIG_LIB_ENABLED: true
```
!!! warning "Solr 10"
From the Solr 10 release onwards, this opt-in ENV will no longer be available.
If Dovecot has not updated their example Solr config ([upstream PR][dovecot::pr::solr-config-lib]), you will need to manually modify the Solr XML config to remove the `<lib>` directives and replace the suggested ENV `SOLR_CONFIG_LIB_ENABLED=true` with `SOLR_MODULES=analysis-extras`.
[docs::user-patches]: ../../config/advanced/override-defaults/user-patches.md
[docs::dovecot::full-text-search]: ../../config/advanced/full-text-search.md
[gh-dms::feature-request::dovecot-solr-package]: https://github.com/docker-mailserver/docker-mailserver/issues/4052
[dockerhub-solr]: https://hub.docker.com/_/solr
[dockerfile-solr-uidgid]: https://github.com/apache/solr-docker/blob/9cd850b72309de05169544395c83a85b329d6b86/9.6/Dockerfile#L89-L92
[github-solr]: https://github.com/apache/solr
[github-dovecot::core-docs]: https://github.com/dovecot/core/tree/main/doc
[solr::9.8::lib-directive]: https://issues.apache.org/jira/browse/SOLR-16781
[dovecot::pr::solr-config-lib]: https://github.com/dovecot/core/pull/238

View File

@ -136,6 +136,8 @@ The below guidance is focused on configuring [Traefik][traefik-web], but the adv
Postfix and Dovecot are both compatible with PROXY protocol v1 and v2. Postfix and Dovecot are both compatible with PROXY protocol v1 and v2.
#### Ports
??? abstract "Technical Details - Ports (Traefik config)" ??? abstract "Technical Details - Ports (Traefik config)"
!!! info "Explicit TLS (STARTTLS)" !!! info "Explicit TLS (STARTTLS)"
@ -259,6 +261,12 @@ The below guidance is focused on configuring [Traefik][traefik-web], but the adv
postconf -P 12525/inet/postscreen_upstream_proxy_protocol=haproxy 12525/inet/syslog_name=smtp-proxyprotocol postconf -P 12525/inet/postscreen_upstream_proxy_protocol=haproxy 12525/inet/syslog_name=smtp-proxyprotocol
``` ```
Supporting port 25 with an additional PROXY protocol port will also require a `postfix-main.cf` override line for `postscreen` to work correctly:
```cf title="docker-data/dms/config/postfix-main.cf"
postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
```
--- ---
Dovecot is mostly the same as before: Dovecot is mostly the same as before:
@ -380,7 +388,7 @@ While PROXY protocol works well with the reverse proxy, you may have some contai
[docs::overrides::postfix]: ../../config/advanced/override-defaults/postfix.md [docs::overrides::postfix]: ../../config/advanced/override-defaults/postfix.md
[docs::overrides::user-patches]: ../../config/advanced/override-defaults/user-patches.md [docs::overrides::user-patches]: ../../config/advanced/override-defaults/user-patches.md
[docs::ipv6::security-risks]: ../../config/advanced/ipv6.md#what-can-go-wrong [docs::ipv6::security-risks]: ../../config/advanced/ipv6.md#what-can-go-wrong
[docs::tls::traefik]: ../../config/security/ssl.md#traefik-v2 [docs::tls::traefik]: ../../config/security/ssl.md#traefik
[docs::env::permit_docker]: ../../config/environment.md#permit_docker [docs::env::permit_docker]: ../../config/environment.md#permit_docker
[gh-dms::dns-rewrite-example]: https://github.com/docker-mailserver/docker-mailserver/issues/3866#issuecomment-1928877236 [gh-dms::dns-rewrite-example]: https://github.com/docker-mailserver/docker-mailserver/issues/3866#issuecomment-1928877236

View File

@ -156,7 +156,7 @@ If working with HTTP in Lua, setting `debug = true;` when initiating `dovecot.ht
Note that Lua runs compiled bytecode, and that scripts will be compiled when they are initially started. Once compiled, the bytecode is cached and changes in the Lua script will not be processed automatically. Dovecot will reload its configuration and clear its cached Lua bytecode when running `docker exec CONTAINER_NAME dovecot reload`. A (changed) Lua script will be compiled to bytecode the next time it is executed after running the Dovecot reload command. Note that Lua runs compiled bytecode, and that scripts will be compiled when they are initially started. Once compiled, the bytecode is cached and changes in the Lua script will not be processed automatically. Dovecot will reload its configuration and clear its cached Lua bytecode when running `docker exec CONTAINER_NAME dovecot reload`. A (changed) Lua script will be compiled to bytecode the next time it is executed after running the Dovecot reload command.
[docs::auth-ldap]: ../../config/advanced/auth-ldap.md [docs::auth-ldap]: ../../config/account-management/provisioner/ldap.md
[docs::dovecot-override-configuration]: ../../config/advanced/override-defaults/dovecot.md#override-configuration [docs::dovecot-override-configuration]: ../../config/advanced/override-defaults/dovecot.md#override-configuration
[docs::dovecot-add-configuration]: ../../config/advanced/override-defaults/dovecot.md#add-configuration [docs::dovecot-add-configuration]: ../../config/advanced/override-defaults/dovecot.md#add-configuration
[docs::faq-alter-running-dms-instance-without-container-relaunch]: ../../faq.md#how-to-alter-a-running-dms-instance-without-relaunching-the-container [docs::faq-alter-running-dms-instance-without-container-relaunch]: ../../faq.md#how-to-alter-a-running-dms-instance-without-relaunching-the-container

View File

@ -21,10 +21,6 @@ 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"
@ -56,9 +52,44 @@ 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 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 (`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.
[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
@ -66,3 +97,10 @@ 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.
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`. 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`.
- 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.
@ -164,7 +164,7 @@ You definitely want to setup TLS. Please refer to [our documentation about TLS][
You should add at least one [alias][docs-aliases], the [_postmaster alias_][docs-env-postmaster]. This is a common convention, but not strictly required. You should add at least one [alias][docs-aliases], the [_postmaster alias_][docs-env-postmaster]. This is a common convention, but not strictly required.
[docs-aliases]: ./config/user-management.md#aliases [docs-aliases]: ./config/account-management/overview.md#aliases
[docs-env-postmaster]: ./config/environment.md#postmaster_address [docs-env-postmaster]: ./config/environment.md#postmaster_address
```bash ```bash

View File

@ -104,6 +104,13 @@ markdown_extensions:
lang: cfg lang: cfg
- name: env - name: env
lang: properties lang: properties
# We only show PHP snippets, requires config change to work:
# https://squidfunk.github.io/mkdocs-material/setup/extensions/python-markdown-extensions/#highlight
# https://facelessuser.github.io/pymdown-extensions/extensions/highlight/#extended-pygments-lexer-options
- name: php
lang: php
options:
startinline: true
# A variant that sometimes has nicer syntax highlighting: # A variant that sometimes has nicer syntax highlighting:
- name: cf-extra - name: cf-extra
lang: linuxconfig lang: linuxconfig
@ -131,7 +138,14 @@ nav:
- 'Usage': usage.md - 'Usage': usage.md
- 'Configuration': - 'Configuration':
- 'Environment Variables': config/environment.md - 'Environment Variables': config/environment.md
- 'User Management': config/user-management.md - 'Account Management':
- 'Overview': config/account-management/overview.md
- 'Provisioner':
- 'File Based': config/account-management/provisioner/file.md
- 'LDAP Service': config/account-management/provisioner/ldap.md
- 'Supplementary':
- 'Master Accounts': config/account-management/supplementary/master-accounts.md
- 'OAuth2 Authentication': config/account-management/supplementary/oauth2.md
- 'Best Practices': - 'Best Practices':
- 'Auto-discovery': config/best-practices/autodiscover.md - 'Auto-discovery': config/best-practices/autodiscover.md
- 'DKIM, DMARC & SPF': config/best-practices/dkim_dmarc_spf.md - 'DKIM, DMARC & SPF': config/best-practices/dkim_dmarc_spf.md
@ -153,8 +167,6 @@ nav:
- 'Dovecot': config/advanced/override-defaults/dovecot.md - 'Dovecot': config/advanced/override-defaults/dovecot.md
- 'Postfix': config/advanced/override-defaults/postfix.md - 'Postfix': config/advanced/override-defaults/postfix.md
- 'Modifications via Script': config/advanced/override-defaults/user-patches.md - 'Modifications via Script': config/advanced/override-defaults/user-patches.md
- 'LDAP Authentication': config/advanced/auth-ldap.md
- 'OAuth2 Authentication': config/advanced/auth-oauth2.md
- 'Email Filtering with Sieve': config/advanced/mail-sieve.md - 'Email Filtering with Sieve': config/advanced/mail-sieve.md
- 'Email Gathering with Fetchmail': config/advanced/mail-fetchmail.md - 'Email Gathering with Fetchmail': config/advanced/mail-fetchmail.md
- 'Email Gathering with Getmail': config/advanced/mail-getmail.md - 'Email Gathering with Getmail': config/advanced/mail-getmail.md
@ -166,7 +178,6 @@ nav:
- 'Kubernetes': config/advanced/kubernetes.md - 'Kubernetes': config/advanced/kubernetes.md
- 'IPv6': config/advanced/ipv6.md - 'IPv6': config/advanced/ipv6.md
- 'Podman': config/advanced/podman.md - 'Podman': config/advanced/podman.md
- 'Dovecot Master Accounts': config/advanced/dovecot-master-accounts.md
- 'Examples': - 'Examples':
- 'Tutorials': - 'Tutorials':
- 'Basic Installation': examples/tutorials/basic-installation.md - 'Basic Installation': examples/tutorials/basic-installation.md
@ -174,6 +185,7 @@ nav:
- 'Crowdsec': examples/tutorials/crowdsec.md - 'Crowdsec': examples/tutorials/crowdsec.md
- 'Building your own Docker image': examples/tutorials/docker-build.md - 'Building your own Docker image': examples/tutorials/docker-build.md
- 'Blog Posts': examples/tutorials/blog-posts.md - 'Blog Posts': examples/tutorials/blog-posts.md
- 'Dovecot FTS with Apache Solr': examples/tutorials/dovecot-solr.md
- 'Use Cases': - 'Use Cases':
- 'Forward-Only Mail-Server with LDAP': examples/use-cases/forward-only-mailserver-with-ldap-authentication.md - 'Forward-Only Mail-Server with LDAP': examples/use-cases/forward-only-mailserver-with-ldap-authentication.md
- 'Customize IMAP Folders': examples/use-cases/imap-folders.md - 'Customize IMAP Folders': examples/use-cases/imap-folders.md

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 his own or his alias addresses. Addresses with extension delimiters(http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages. # 1 => Mail spoofing denied. Each user may only send with their own or their alias addresses. Addresses with extension delimiters(http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages.
SPOOF_PROTECTION= SPOOF_PROTECTION=
# Enables the Sender Rewriting Scheme. SRS is needed if your mail server acts as forwarder. See [postsrsd](https://github.com/roehling/postsrsd/blob/master/README.md#sender-rewriting-scheme-crash-course) for further explanation. # Enables the Sender Rewriting Scheme. SRS is needed if your mail server acts as forwarder. See [postsrsd](https://github.com/roehling/postsrsd/blob/main/README.rst) for further explanation.
# - **0** => Disabled # - **0** => Disabled
# - 1 => Enabled # - 1 => Enabled
ENABLE_SRS=0 ENABLE_SRS=0
@ -130,8 +130,9 @@ ENABLE_IMAP=1
# **0** => Disabled # **0** => Disabled
ENABLE_CLAMAV=0 ENABLE_CLAMAV=0
# Add the value as a prefix to the mail subject when spam is detected. # Add the value of this ENV as a prefix to the mail subject when spam is detected.
# NOTE: By default spam is delivered to a junk folder, reducing the value of a subject prefix for spam. # NOTE: This subject prefix may be redundant (by default spam is delivered to a junk folder).
# It provides value when your junk mail is stored alongside legitimate mail instead of a separate location (like with `SPAMASSASSIN_SPAM_TO_INBOX=1` or `MOVE_SPAM_TO_JUNK=0` or a POP3 only setup, without IMAP).
# NOTE: When not using Docker Compose, other CRI may not support quote-wrapping the value here to preserve any trailing white-space. # NOTE: When not using Docker Compose, other CRI may not support quote-wrapping the value here to preserve any trailing white-space.
SPAM_SUBJECT= SPAM_SUBJECT=
@ -266,7 +267,7 @@ POSTFIX_DAGENT=
# empty => 0 # empty => 0
POSTFIX_MAILBOX_SIZE_LIMIT= POSTFIX_MAILBOX_SIZE_LIMIT=
# See https://docker-mailserver.github.io/docker-mailserver/edge/config/user-management/accounts/#notes # See https://docker-mailserver.github.io/docker-mailserver/latest/config/account-management/overview/#quotas
# 0 => Dovecot quota is disabled # 0 => Dovecot quota is disabled
# 1 => Dovecot quota is enabled # 1 => Dovecot quota is enabled
ENABLE_QUOTAS=1 ENABLE_QUOTAS=1
@ -429,7 +430,7 @@ FETCHMAIL_PARALLEL=0
# - 1 => Enabled # - 1 => Enabled
ENABLE_GETMAIL=0 ENABLE_GETMAIL=0
# The number of minutes for the interval. Min: 1; Max: 30. # The number of minutes for the interval. Min: 1; Default: 5.
GETMAIL_POLL=5 GETMAIL_POLL=5
# ----------------------------------------------- # -----------------------------------------------
@ -507,7 +508,7 @@ DOVECOT_MAILBOX_FORMAT=maildir
# empty => no # empty => no
# yes => Allow bind authentication for LDAP # yes => Allow bind authentication for LDAP
# https://wiki.dovecot.org/AuthDatabase/LDAP/AuthBinds # https://doc.dovecot.org/2.4.0/core/config/auth/databases/ldap.html#authentication-bind
DOVECOT_AUTH_BIND= DOVECOT_AUTH_BIND=
# ----------------------------------------------- # -----------------------------------------------

View File

@ -7,8 +7,7 @@ function _main() {
_require_n_parameters_or_print_usage 1 "${@}" _require_n_parameters_or_print_usage 1 "${@}"
local MAIL_ACCOUNT="${1}" local MAIL_ACCOUNT="${1}"
shift local PASSWD="${2}"
local PASSWD="${*}"
_manage_accounts_dovecotmaster_create "${MAIL_ACCOUNT}" "${PASSWD}" _manage_accounts_dovecotmaster_create "${MAIL_ACCOUNT}" "${PASSWD}"
} }

View File

@ -7,8 +7,7 @@ function _main() {
_require_n_parameters_or_print_usage 1 "${@}" _require_n_parameters_or_print_usage 1 "${@}"
local MAIL_ACCOUNT="${1}" local MAIL_ACCOUNT="${1}"
shift local PASSWD="${2}"
local PASSWD="${*}"
_manage_accounts_create "${MAIL_ACCOUNT}" "${PASSWD}" _manage_accounts_create "${MAIL_ACCOUNT}" "${PASSWD}"

View File

@ -8,8 +8,7 @@ function _main() {
local DOMAIN="${1}" local DOMAIN="${1}"
local RELAY_ACCOUNT="${2}" local RELAY_ACCOUNT="${2}"
shift 2 local PASSWD="${3}"
local PASSWD="${*}"
_validate_parameters _validate_parameters
_add_relayhost_credentials _add_relayhost_credentials

View File

@ -5,9 +5,20 @@ source /usr/local/bin/helpers/log.sh
# shellcheck source=../scripts/startup/setup-stack.sh # shellcheck source=../scripts/startup/setup-stack.sh
source /usr/local/bin/setup.d/getmail.sh source /usr/local/bin/setup.d/getmail.sh
_setup_getmail # Setup getmail, even if not enabled.
ENABLE_GETMAIL=1 _setup_getmail
GETMAILDIR=/tmp/docker-mailserver/getmail # Directory, where "oldmail" files are stored.
for FILE in /etc/getmailrc.d/getmailrc*; do # Getmail stores its state - its "memory" of what it has seen in your POP/IMAP account - in the oldmail files.
getmail --getmaildir "${GETMAILDIR}" --rcfile "${FILE}" --dump | tail -n +6 GETMAIL_DIR=/var/lib/getmail
# If no matching filenames are found, and the shell option nullglob is disabled, the word is left unchanged.
# If the nullglob option is set, and no matches are found, the word is removed.
shopt -s nullglob
# Dump configuration from each RC file.
for RC_FILE in /etc/getmailrc.d/*; do
echo "${RC_FILE##*/}:"
echo
getmail --getmaildir "${GETMAIL_DIR}" --rcfile "${RC_FILE}" --dump | tail -n +6
done done

View File

@ -19,7 +19,11 @@ function _main() {
for MAIL_ACCOUNT in "${@}"; do for MAIL_ACCOUNT in "${@}"; do
_account_should_already_exist _account_should_already_exist
[[ ${MAILDEL} -eq 1 ]] && _remove_maildir "${MAIL_ACCOUNT}" if [[ ${MAILDEL} -eq 1 ]]; then
_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"
@ -31,7 +35,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 deleted" _log 'info' "'${MAIL_ACCOUNT}' and associated data (aliases, quotas) deleted"
done done
} }
@ -43,14 +47,14 @@ ${ORANGE}USAGE${RESET}
${ORANGE}OPTIONS${RESET} ${ORANGE}OPTIONS${RESET}
-y -y
Skip prompt by approving to ${LWHITE}delete all mail storage${RESET} for the account(s). Skip prompt by approving to ${LWHITE}delete all mail data${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 storage for that account. optionally the mailbox data 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}
@ -87,12 +91,10 @@ 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 as well (removing all mails)? [Y/n] " MAILDEL_CHOSEN read -r -p "Do you want to delete the mailbox data as well (removing all mails)? [y/N] " MAILDEL_CHOSEN
# TODO: Why would MAILDEL be set to true if MAILDEL_CHOSEN is empty? # Delete mailbox data only if the user provides explicit confirmation.
if [[ ${MAILDEL_CHOSEN} =~ (y|Y|yes|Yes) ]] || [[ -z ${MAILDEL_CHOSEN} ]]; then [[ ${MAILDEL_CHOSEN,,} == "y" ]] && MAILDEL=1
MAILDEL=1
fi
fi fi
} }
@ -103,10 +105,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 directory '${MAIL_ACCOUNT_STORAGE_DIR}' does not exist" [[ ! -d ${MAIL_ACCOUNT_STORAGE_DIR} ]] && _exit_with_error "Mailbox data directory '${MAIL_ACCOUNT_STORAGE_DIR}' does not exist"
_log 'info' "Deleting Mailbox: '${MAIL_ACCOUNT_STORAGE_DIR}'" _log 'info' "Deleting mailbox data: '${MAIL_ACCOUNT_STORAGE_DIR}'"
rm -R "${MAIL_ACCOUNT_STORAGE_DIR}" || _exit_with_error 'Mailbox could not be deleted' rm -R "${MAIL_ACCOUNT_STORAGE_DIR}" || _exit_with_error 'Mailbox data 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

@ -1,8 +0,0 @@
#! /bin/bash
GETMAILDIR=/tmp/docker-mailserver/getmail
for FILE in /etc/getmailrc.d/getmailrc*; do
if ! pgrep -f "${FILE}$" &>/dev/null; then
getmail --getmaildir "${GETMAILDIR}" --rcfile "${FILE}"
fi
done

View File

@ -12,9 +12,17 @@ if [[ -f /etc/dms-settings ]] && [[ $(_get_dms_env_value 'ENABLE_RSPAMD') -eq 1
fi fi
fi fi
KEYSIZE=2048 function _main() {
SELECTOR=mail # Default parameters (updated by `_parse_arguments()`):
DOMAINS= local KEYSIZE=2048
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})
@ -57,122 +65,171 @@ ${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 exit status 2. errors, the script will exit early with a non-zero exit status.
" "
} }
_require_n_parameters_or_print_usage 0 "${@}" function _parse_arguments() {
# Parse the command args through iteration:
while [[ ${#} -gt 0 ]]; do while [[ ${#} -gt 0 ]]; do
case "${1}" in case "${1}" in
( 'keysize' ) ( 'keysize' )
if [[ -n ${2+set} ]]; then if [[ -n ${2:-} ]]; then
KEYSIZE="${2}" KEYSIZE="${2}"
shift _log 'trace' "Keysize set to '${KEYSIZE}'"
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+set} ]]; then if [[ -n ${2:-} ]]; then
# shellcheck disable=SC2034
SELECTOR="${2}" SELECTOR="${2}"
shift _log 'trace' "Selector set to '${SELECTOR}'"
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+set} ]]; then if [[ -n ${2:-} ]]; then
DOMAINS="${2}" DMS_DOMAINS="${2}"
shift _log 'trace' "Domain(s) set to '${DMS_DOMAINS}'"
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 __usage
_exit_with_error "Unknown options '${1}' ${2:+and \'${2}\'}" exit 0
;; ;;
( * )
__usage
_exit_with_error "Unknown option(s) ${*}"
;;
esac esac
# Discard these two args (option + value) now that they've been processed:
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
mkdir -p "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}" _create_dkim_key "${DKIM_DOMAIN}"
if [[ ! -f "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private" ]]; then # Create / Update OpenDKIM configs with new DKIM key:
_log 'info' "Creating DKIM private key '/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private'" ENTRY_KEY="${SELECTOR}._domainkey.${DKIM_DOMAIN}"
KEY_TABLE_ENTRY="${ENTRY_KEY} ${DKIM_DOMAIN}:${SELECTOR}:/etc/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private"
SIGNING_TABLE_ENTRY="*@${DKIM_DOMAIN} ${ENTRY_KEY}"
# If no existing entry, add one:
if ! grep -q "${KEY_TABLE_ENTRY}" "${KEY_TABLE_FILE}"; then
echo "${KEY_TABLE_ENTRY}" >> "${KEY_TABLE_FILE}"
fi
if ! grep -q "${SIGNING_TABLE_ENTRY}" "${SIGNING_TABLE_FILE}"; then
echo "${SIGNING_TABLE_ENTRY}" >> "${SIGNING_TABLE_FILE}"
fi
done < <(_get_valid_lines_from_file "${DATABASE_VHOST}")
# No longer needed, remove:
rm "${DATABASE_VHOST}"
# Ensure ownership is consistent for all content belonging to the base directory,
# During container startup, an internal copy will be made via `_setup_opendkim()`
# with ownership we expect, while this chown is for the benefit of the users ownership.
chown -R "$(stat -c '%U:%G' "${OPENDKIM_BASE_DIR}")" "${OPENDKIM_BASE_DIR}"
}
# Prepare a file with one domain per line (iterated via while loop as DKIM_DOMAIN):
# Depends on methods from `scripts/helpers/postfix.sh`:
DATABASE_VHOST='/tmp/vhost.dkim'
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="/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}" --directory="${OPENDKIM_DOMAINKEY_DIR}"
fi fi
}
# fix permissions to use the same user:group as /tmp/docker-mailserver/opendkim/keys OPENDKIM_BASE_DIR='/tmp/docker-mailserver/opendkim'
chown -R "$(stat -c '%U:%G' /tmp/docker-mailserver/opendkim/keys)" "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}" KEY_TABLE_FILE="${OPENDKIM_BASE_DIR}/KeyTable"
SIGNING_TABLE_FILE="${OPENDKIM_BASE_DIR}/SigningTable"
TRUSTED_HOSTS_FILE="${OPENDKIM_BASE_DIR}/TrustedHosts"
# Create configs if missing:
function _create_opendkim_configs() {
mkdir -p "${OPENDKIM_BASE_DIR}"
local OPENDKIM_CONFIGS=(
"${KEY_TABLE_FILE}"
"${SIGNING_TABLE_FILE}"
"${TRUSTED_HOSTS_FILE}"
)
# write to KeyTable if necessary # Only create if the file doesn't exist (avoids modifying mtime):
KEYTABLEENTRY="${SELECTOR}._domainkey.${DKIM_DOMAIN} ${DKIM_DOMAIN}:${SELECTOR}:/etc/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private" for FILE in "${OPENDKIM_CONFIGS[@]}"; do
if [[ ! -f "/tmp/docker-mailserver/opendkim/KeyTable" ]]; then if [[ ! -f "${FILE}" ]]; then
_log 'debug' 'Creating DKIM KeyTable' _log 'debug' "Creating OpenDKIM config '${FILE}'"
echo "${KEYTABLEENTRY}" >/tmp/docker-mailserver/opendkim/KeyTable touch "${FILE}"
else
if ! grep -q "${KEYTABLEENTRY}" "/tmp/docker-mailserver/opendkim/KeyTable"; then
echo "${KEYTABLEENTRY}" >>/tmp/docker-mailserver/opendkim/KeyTable
fi
fi fi
done
# write to SigningTable if necessary # If file exists but is empty, add default hosts to trust:
SIGNINGTABLEENTRY="*@${DKIM_DOMAIN} ${SELECTOR}._domainkey.${DKIM_DOMAIN}" if [[ ! -s "${TRUSTED_HOSTS_FILE}" ]]; then
if [[ ! -f /tmp/docker-mailserver/opendkim/SigningTable ]]; then _log 'debug' 'Adding default trust to OpenDKIM TrustedHosts config'
_log 'debug' 'Creating DKIM SigningTable' echo "127.0.0.1" > "${TRUSTED_HOSTS_FILE}"
echo "*@${DKIM_DOMAIN} ${SELECTOR}._domainkey.${DKIM_DOMAIN}" >/tmp/docker-mailserver/opendkim/SigningTable echo "localhost" >> "${TRUSTED_HOSTS_FILE}"
else
if ! grep -q "${SIGNINGTABLEENTRY}" /tmp/docker-mailserver/opendkim/SigningTable; then
echo "${SIGNINGTABLEENTRY}" >>/tmp/docker-mailserver/opendkim/SigningTable
fi fi
fi }
done < <(_get_valid_lines_from_file "${DATABASE_VHOST}")
# create TrustedHosts if missing _main "${@}"
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

@ -55,6 +55,10 @@ ${ORANGE}EXAMPLES${RESET}
${LWHITE}setup config dkim domain example.com${RESET} ${LWHITE}setup config dkim domain example.com${RESET}
Generate the DKIM key for a different domain (example.com). Generate the DKIM key for a different domain (example.com).
${LWHITE}setup config dkim keytype ed25519 domain edward.com selector elliptic-test${RESET}
Generate the DKIM key using the ED25519 elliptic curve for the domain
edward.com and the selector elliptic-test.
${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 a non-zero exit status.

View File

@ -64,6 +64,7 @@ ${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}
@ -150,6 +151,7 @@ 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

@ -7,8 +7,7 @@ function _main() {
_require_n_parameters_or_print_usage 1 "${@}" _require_n_parameters_or_print_usage 1 "${@}"
local MAIL_ACCOUNT="${1}" local MAIL_ACCOUNT="${1}"
shift local PASSWD="${2}"
local PASSWD="${*}"
_manage_accounts_dovecotmaster_update "${MAIL_ACCOUNT}" "${PASSWD}" _manage_accounts_dovecotmaster_update "${MAIL_ACCOUNT}" "${PASSWD}"
} }

View File

@ -7,8 +7,7 @@ function _main() {
_require_n_parameters_or_print_usage 1 "${@}" _require_n_parameters_or_print_usage 1 "${@}"
local MAIL_ACCOUNT="${1}" local MAIL_ACCOUNT="${1}"
shift local PASSWD="${2}"
local PASSWD="${*}"
_manage_accounts_update "${MAIL_ACCOUNT}" "${PASSWD}" _manage_accounts_update "${MAIL_ACCOUNT}" "${PASSWD}"
} }

View File

@ -6,7 +6,7 @@
passdb { passdb {
driver = ldap driver = ldap
mechanism = plain login mechanisms = plain login
# Path for LDAP configuration file, see example-config/dovecot-ldap.conf.ext # Path for LDAP configuration file, see example-config/dovecot-ldap.conf.ext
args = /etc/dovecot/dovecot-ldap.conf.ext args = /etc/dovecot/dovecot-ldap.conf.ext

View File

@ -0,0 +1,47 @@
#!/bin/bash
# shellcheck source=../scripts/helpers/log.sh
source /usr/local/bin/helpers/log.sh
# Directory, where "oldmail" files are stored.
# getmail stores its state - its "memory" of what it has seen in your POP/IMAP account - in the oldmail files.
GETMAIL_DIR=/var/lib/getmail
# Kill all child processes on EXIT.
# Otherwise 'supervisorctl restart getmail' leads to zombie 'sleep' processes.
trap 'pkill --parent ${$}' EXIT
function _syslog_error() {
logger --priority mail.err --tag getmail "${1}"
}
function _stop_service() {
_syslog_error "Stopping service"
exec supervisorctl stop getmail
}
# Verify the correct value for GETMAIL_POLL. Valid are any numbers greater than 0.
if [[ ! ${GETMAIL_POLL} =~ ^[0-9]+$ ]] || [[ ${GETMAIL_POLL} -lt 1 ]]; then
_syslog_error "Invalid value for GETMAIL_POLL: ${GETMAIL_POLL}"
_stop_service
fi
# If no matching filenames are found, and the shell option nullglob is disabled, the word is left unchanged.
# If the nullglob option is set, and no matches are found, the word is removed.
shopt -s nullglob
# Run each getmailrc periodically.
while :; do
for RC_FILE in /etc/getmailrc.d/*; do
_log 'debug' "Processing ${RC_FILE}"
getmail --getmaildir "${GETMAIL_DIR}" --rcfile "${RC_FILE}"
done
# Stop service if no configuration is found.
if [[ -z ${RC_FILE} ]]; then
_syslog_error 'No configuration found'
_stop_service
fi
sleep "${GETMAIL_POLL}m"
done

View File

@ -0,0 +1,11 @@
# https://getmail6.org/configuration.html#conf-options
[options]
verbose = 0
read_all = false
delete = false
max_messages_per_session = 500
received = false
delivered_to = false
message_log_syslog = true

View File

@ -1,2 +1,3 @@
# ignore output from dovecot-fts-xapian about successfully indexed messages # ignore output from dovecot-fts-xapian about successfully indexed messages
dovecot: indexer-worker\([^\)]+\).*Indexed dovecot: indexer-worker\([^\)]+\).*Indexed
dovecot: indexer-worker\([^\)]+\).*FTS Xapian: Waiting for all pending documents to be processed

View File

@ -68,9 +68,10 @@ 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:
# reject_unknown_sender_domain: https://github.com/docker-mailserver/docker-mailserver/issues/3716#issuecomment-1868033234 # Custom sender restrictions overview: https://github.com/docker-mailserver/docker-mailserver/pull/4379#issuecomment-2670365917
# `reject_unknown_sender_domain`: https://github.com/docker-mailserver/docker-mailserver/issues/3716#issuecomment-1868033234
dms_smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unknown_sender_domain dms_smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unknown_sender_domain
# Submission ports 587 and 465 support for SPOOF_PROTECTION=1 # `SPOOF_PROTECTION=1` support requires prepending `reject_authenticated_sender_login_mismatch`
mua_sender_restrictions = reject_authenticated_sender_login_mismatch, $dms_smtpd_sender_restrictions 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,4 +8,5 @@
/^\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
/^\s*Message-Id:/i PREPEND X-MS-Reactions: disallow

View File

@ -11,28 +11,39 @@ 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 _compile_dovecot_fts_xapian() { function _install_build_deps() {
apt-get "${QUIET}" update apt-get "${QUIET}" update
apt-get "${QUIET}" --no-install-recommends install \ apt-get "${QUIET}" install --no-install-recommends \
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
local XAPIAN_VERSION='1.7.12'
curl -sSfL -o dovecot-fts-xapian.tar.gz \
"https://github.com/grosjo/fts-xapian/releases/download/${XAPIAN_VERSION}/dovecot-fts-xapian-${XAPIAN_VERSION}.tar.gz"
tar xf dovecot-fts-xapian.tar.gz
cd "fts-xapian-${XAPIAN_VERSION}"
USER=root dh_make -p "dovecot-fts-xapian-${XAPIAN_VERSION}" --single --native --copyright gpl2 -y
rm debian/*.ex
cp PACKAGES/DEB/control debian/
cp PACKAGES/DEB/changelog debian/
cp PACKAGES/DEB/compat debian/
sed -i -E "s|(dovecot-fts-xapian)-[1-9\.-]+|\1-${XAPIAN_VERSION}|g" debian/control
sed -i -E "s|(dovecot-fts-xapian)-[1-9\.-]+ \(.*\)(.*)|\1-${XAPIAN_VERSION} (${XAPIAN_VERSION})\2|g" debian/changelog
debuild -us -uc -B | tee /tmp/debuild.log 2>&1
} }
_compile_dovecot_fts_xapian function _build_package() {
local XAPIAN_VERSION='1.9'
curl -fsSL "https://github.com/grosjo/fts-xapian/releases/download/${XAPIAN_VERSION}/dovecot-fts-xapian-${XAPIAN_VERSION}.tar.gz" \
| tar -xz
cd "fts-xapian-${XAPIAN_VERSION}"
# 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
# Add required package metadata:
# https://www.debian.org/doc/manuals/maint-guide/dreq.en.html#control
curl -fsSL https://raw.githubusercontent.com/grosjo/fts-xapian/refs/tags/1.7.16/PACKAGES/DEB/control > debian/control
# 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:
# https://manpages.debian.org/bookworm/devscripts/debuild.1.en.html
# https://manpages.debian.org/bookworm/dpkg-dev/dpkg-buildpackage.1.en.html
debuild --no-sign --build=any | tee /tmp/debuild.log 2>&1
}
_install_build_deps
_build_package

View File

@ -24,30 +24,58 @@ 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=(
apt-utils # avoid useless warnings # Avoid logging unnecessary warnings:
apt-transport-https ca-certificates curl gnupg # required for adding PPAs apt-utils
systemd-standalone-sysusers # avoid problems with SA / Amavis (https://github.com/docker-mailserver/docker-mailserver/pull/3403#pullrequestreview-1596689953) # Required for adding third-party repos (/etc/apt/sources.list.d) as alternative package sources (eg: Dovecot CE and Rspamd):
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='v1.3.0' local JAQ_TAG='v2.1.0'
curl -sSfL "https://github.com/01mf02/jaq/releases/download/${JAQ_TAG}/jaq-${JAQ_TAG}-$(uname -m)-unknown-linux-gnu" -o /usr/bin/jaq curl -sSfL "https://github.com/01mf02/jaq/releases/download/${JAQ_TAG}/jaq-$(uname -m)-unknown-linux-gnu" -o /usr/local/bin/jaq
chmod +x /usr/bin/jaq chmod +x /usr/local/bin/jaq
_log 'trace' 'Installing step'
local STEP_RELEASE='0.28.2'
curl -sSfL "https://github.com/smallstep/cli/releases/download/v${STEP_RELEASE}/step_linux_${STEP_RELEASE}_${ARCH_B}.tar.gz" \
| tar -xz --directory /usr/local/bin --no-same-owner --strip-components=2 "step_${STEP_RELEASE}/bin/step"
_log 'trace' 'Installing swaks' _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" | tar -xz curl -sSfL "https://github.com/jetmore/swaks/releases/download/v${SWAKS_VERSION}/${SWAKS_RELEASE}.tar.gz" \
mv "${SWAKS_RELEASE}/swaks" /usr/local/bin | tar -xz --directory /usr/local/bin --no-same-owner --strip-components=1 "${SWAKS_RELEASE}/swaks"
rm -r "${SWAKS_RELEASE}"
} }
function _install_postfix() { function _install_postfix() {
@ -73,9 +101,6 @@ function _install_packages() {
clamav clamav-daemon clamav clamav-daemon
# spamassassin is used only with amavisd-new, while pyzor + razor are used by spamassasin # spamassassin is used only with amavisd-new, while pyzor + razor are used by spamassasin
amavisd-new spamassassin pyzor razor amavisd-new spamassassin pyzor razor
# the following packages are all for Fail2Ban
# https://github.com/docker-mailserver/docker-mailserver/pull/3403#discussion_r1306581431
fail2ban python3-pyinotify python3-dnspython
) )
# predominantly for Amavis support # predominantly for Amavis support
@ -121,7 +146,7 @@ function _install_packages() {
bind9-dnsutils iputils-ping less nano bind9-dnsutils iputils-ping less nano
) )
apt-get "${QUIET}" --no-install-recommends install \ apt-get "${QUIET}" install --no-install-recommends \
"${ANTI_VIRUS_SPAM_PACKAGES[@]}" \ "${ANTI_VIRUS_SPAM_PACKAGES[@]}" \
"${CODECS_PACKAGES[@]}" \ "${CODECS_PACKAGES[@]}" \
"${MISCELLANEOUS_PACKAGES[@]}" \ "${MISCELLANEOUS_PACKAGES[@]}" \
@ -138,46 +163,98 @@ function _install_dovecot() {
dovecot-pop3d dovecot-sieve dovecot-pop3d dovecot-sieve
) )
# Dovecot packages for community supported features. # Additional Dovecot packages for supporting the DMS community (docs-only guide contributions).
DOVECOT_PACKAGES+=(dovecot-auth-lua) DOVECOT_PACKAGES+=(dovecot-auth-lua)
# Dovecot's deb community repository only provides x86_64 packages, so do not include it # (Opt-in via ENV) Change repo source for dovecot packages to a third-party repo maintained by Dovecot.
# when building for another architecture. # NOTE: AMD64 / x86_64 is the only supported arch from the Dovecot CE repo (thus noDMS built for ARM64 / aarch64)
# 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
_log 'trace' 'Using Dovecot community repository' # WARNING: Repo only provides Debian Bookworm package support for Dovecot CE 2.4+.
curl -sSfL https://repo.dovecot.org/DOVECOT-REPO-GPG | gpg --import # As Debian Bookworm only packages Dovecot 2.3.x, building DMS with this alternative package repo may not yet be compatible with DMS:
gpg --export ED409DA1 > /etc/apt/trusted.gpg.d/dovecot.gpg # - 2.3.19: https://salsa.debian.org/debian/dovecot/-/tree/stable/bookworm
echo "deb https://repo.dovecot.org/ce-2.3-latest/debian/${VERSION_CODENAME} ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/dovecot.list # - 2.3.21: https://salsa.debian.org/debian/dovecot/-/tree/stable/bookworm-backports
_log 'trace' 'Updating Dovecot package signatures' _log 'trace' 'Adding third-party package repository (Dovecot)'
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
# Additional community package needed for Lua support if the Dovecot community repository is used. # This repo instead provides `dovecot-auth-lua` as a transitional package to `dovecot-lua`,
# 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}" --no-install-recommends install "${DOVECOT_PACKAGES[@]}" apt-get "${QUIET}" install --no-install-recommends "${DOVECOT_PACKAGES[@]}"
# dependency for fts_xapian # Runtime dependency for fts_xapian (built via `compile.sh`):
apt-get "${QUIET}" --no-install-recommends install libxapian30 apt-get "${QUIET}" install --no-install-recommends libxapian30
} }
function _install_rspamd() { function _install_rspamd() {
_log 'debug' 'Installing Rspamd' # NOTE: DMS only supports the rspamd package via using the third-party repo maintained by Rspamd (AMD64 + ARM64):
_log 'trace' 'Adding Rspamd PPA' # Repo: https://rspamd.com/apt-stable/dists/bookworm/main/
curl -sSfL https://rspamd.com/apt-stable/gpg.key | gpg --dearmor >/etc/apt/trusted.gpg.d/rspamd.gpg # Docs: https://rspamd.com/downloads.html#debian-and-ubuntu-linux
echo \ # NOTE: Debian 12 provides Rspamd 3.4 (too old) and Rspamd discourages it's use
"deb [signed-by=/etc/apt/trusted.gpg.d/rspamd.gpg] http://rspamd.com/apt-stable/ ${VERSION_CODENAME} main" \
>/etc/apt/sources.list.d/rspamd.list
_log 'trace' 'Updating package index after adding PPAs' _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 \
"deb [signed-by=/usr/share/keyrings/upstream-rspamd.gpg] https://rspamd.com/apt-stable/ ${VERSION_CODENAME} main" \
> /etc/apt/sources.list.d/upstream-rspamd.list
# Refresh package index:
apt-get "${QUIET}" update apt-get "${QUIET}" update
_log 'trace' 'Installing actual package' _log 'debug' 'Installing Rspamd'
apt-get "${QUIET}" install rspamd redis-server apt-get "${QUIET}" install rspamd redis-server
} }
function _install_fail2ban() {
local FAIL2BAN_VERSION=1.1.0
local FAIL2BAN_DEB_URL="https://github.com/fail2ban/fail2ban/releases/download/${FAIL2BAN_VERSION}/fail2ban_${FAIL2BAN_VERSION}-1.upstream1_all.deb"
local FAIL2BAN_DEB_ASC_URL="${FAIL2BAN_DEB_URL}.asc"
local FAIL2BAN_GPG_FINGERPRINT='8738 559E 26F6 71DF 9E2C 6D9E 683B F1BE BD0A 882C'
local FAIL2BAN_GPG_PUBLIC_KEY_ID='0x683BF1BEBD0A882C'
local FAIL2BAN_GPG_PUBLIC_KEY_SERVER='hkps://keyserver.ubuntu.com'
_log 'debug' 'Installing Fail2ban'
# 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
gpg --keyserver "${FAIL2BAN_GPG_PUBLIC_KEY_SERVER}" --recv-keys "${FAIL2BAN_GPG_PUBLIC_KEY_ID}" 2>&1
curl -fsSLo fail2ban.deb "${FAIL2BAN_DEB_URL}"
curl -fsSLo fail2ban.deb.asc "${FAIL2BAN_DEB_ASC_URL}"
FINGERPRINT=$(LANG=C gpg --verify fail2ban.deb.asc fail2ban.deb |& sed -n 's#Primary key fingerprint: \(.*\)#\1#p')
if [[ -z ${FINGERPRINT} ]]; then
echo 'ERROR: Invalid GPG signature!' >&2
exit 1
fi
if [[ ${FINGERPRINT} != "${FAIL2BAN_GPG_FINGERPRINT}" ]]; then
echo "ERROR: Wrong GPG fingerprint!" >&2
exit 1
fi
dpkg -i fail2ban.deb 2>&1
rm fail2ban.deb fail2ban.deb.asc
_log 'debug' 'Patching Fail2ban to enable network bans'
# Enable network bans
# https://github.com/docker-mailserver/docker-mailserver/issues/2669
# https://github.com/fail2ban/fail2ban/issues/3125
sedfile -i -r 's/^_nft_add_set = .+/_nft_add_set = <nftables> add set <table_family> <table> <addr_set> \\{ type <addr_type>\\; flags interval\\; \\}/' /etc/fail2ban/action.d/nftables.conf
}
function _post_installation_steps() { function _post_installation_steps() {
_log 'debug' 'Running post-installation steps (cleanup)' _log 'debug' 'Running post-installation steps (cleanup)'
_log 'debug' 'Deleting sensitive files (secrets)' _log 'debug' 'Deleting sensitive files (secrets)'
@ -189,11 +266,6 @@ function _post_installation_steps() {
_log 'trace' 'Removing leftovers from APT' _log 'trace' 'Removing leftovers from APT'
apt-get "${QUIET}" clean apt-get "${QUIET}" clean
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
_log 'debug' 'Patching Fail2ban to enable network bans'
# Enable network bans
# https://github.com/docker-mailserver/docker-mailserver/issues/2669
sedfile -i -r 's/^_nft_add_set = .+/_nft_add_set = <nftables> add set <table_family> <table> <addr_set> \\{ type <addr_type>\\; flags interval\\; \\}/' /etc/fail2ban/action.d/nftables.conf
} }
_pre_installation_steps _pre_installation_steps
@ -202,4 +274,5 @@ _install_postfix
_install_packages _install_packages
_install_dovecot _install_dovecot
_install_rspamd _install_rspamd
_install_fail2ban
_post_installation_steps _post_installation_steps

View File

@ -135,7 +135,8 @@ function _create_dovecot_alias_dummy_accounts() {
fi fi
DOVECOT_USERDB_LINE="${ALIAS}:${REAL_ACC[1]}:${DMS_VMAIL_UID}:${DMS_VMAIL_GID}::/var/mail/${REAL_DOMAINNAME}/${REAL_USERNAME}/home::${REAL_ACC[2]:-}" DOVECOT_USERDB_LINE="${ALIAS}:${REAL_ACC[1]}:${DMS_VMAIL_UID}:${DMS_VMAIL_GID}::/var/mail/${REAL_DOMAINNAME}/${REAL_USERNAME}/home::${REAL_ACC[2]:-}"
if grep -qi "^${ALIAS}:" "${DOVECOT_USERDB_FILE}"; then # Match a full line with `-xF` to avoid regex patterns introducing false positives matching `ALIAS`:
if grep -qixF "${DOVECOT_USERDB_LINE}" "${DOVECOT_USERDB_FILE}"; then
_log 'warn' "Alias '${ALIAS}' will not be added to '${DOVECOT_USERDB_FILE}' twice" _log 'warn' "Alias '${ALIAS}' will not be added to '${DOVECOT_USERDB_FILE}' twice"
else else
echo "${DOVECOT_USERDB_LINE}" >>"${DOVECOT_USERDB_FILE}" echo "${DOVECOT_USERDB_LINE}" >>"${DOVECOT_USERDB_FILE}"

View File

@ -44,7 +44,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 -name "*.sh" -print0) readarray -d '' STAGING_FILES_RSPAMD < <(find "${RSPAMD_DMS_D}" -type f -print0)
STAGING_FILES+=("${STAGING_FILES_RSPAMD[@]}") STAGING_FILES+=("${STAGING_FILES_RSPAMD[@]}")
fi fi
fi fi

View File

@ -98,9 +98,14 @@ function __account_already_exists() {
# Also used by addsaslpassword # Also used by addsaslpassword
function _password_request_if_missing() { function _password_request_if_missing() {
local PASSWD_CONFIRM
if [[ -z ${PASSWD} ]]; then if [[ -z ${PASSWD} ]]; then
read -r -s -p 'Enter Password: ' PASSWD read -r -s -p 'Enter Password: ' PASSWD
echo echo
[[ -z ${PASSWD} ]] && _exit_with_error 'Password must not be empty' [[ -z ${PASSWD} ]] && _exit_with_error 'Password must not be empty'
read -r -s -p 'Confirm Password: ' PASSWD_CONFIRM
echo
[[ ${PASSWD} != "${PASSWD_CONFIRM}" ]] && _exit_with_error 'Passwords do not match!'
fi fi
} }

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
function _exit_with_error() { function _exit_with_error() {
if [[ -n ${1+set} ]]; then if [[ -n ${1:-} ]]; 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+set} ]]; then if [[ -z ${1:-} ]]; 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+set} ]]; then if [[ -z ${2:-} ]]; 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+set} ]]; then if [[ -n ${LOG_LEVEL:-} ]]; 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,14 +111,6 @@ 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+set} ]]; then if [[ -z ${1:-} ]]; 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+set} ]]; then elif [[ -z ${2:-} ]]; 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

@ -1,7 +1,11 @@
#!/bin/bash #!/bin/bash
# When 'pipefail' is enabled, the exit status of the pipeline reflects the exit status of the last command that fails.
# Without 'pipefail', the exit status of a pipeline is determined by the exit status of the last command in the pipeline.
set -o pipefail set -o pipefail
shopt -s globstar inherit_errexit
# Allows the usage of '**' in patterns, e.g. ls **/*
shopt -s globstar
# ------------------------------------------------------------ # ------------------------------------------------------------
# ? >> Sourcing helpers & stacks # ? >> Sourcing helpers & stacks
@ -34,13 +38,11 @@ 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
@ -59,7 +61,6 @@ function _register_functions() {
;; ;;
( 'LDAP' ) ( 'LDAP' )
_environment_variables_ldap
_register_setup_function '_setup_ldap' _register_setup_function '_setup_ldap'
;; ;;
@ -72,15 +73,8 @@ function _register_functions() {
;; ;;
esac esac
if [[ ${ENABLE_OAUTH2} -eq 1 ]]; then [[ ${ENABLE_OAUTH2} -eq 1 ]] && _register_setup_function '_setup_oauth2'
_environment_variables_oauth2 [[ ${ENABLE_SASLAUTHD} -eq 1 ]] && _register_setup_function '_setup_saslauthd'
_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'
@ -94,7 +88,6 @@ 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'
@ -118,14 +111,17 @@ 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_apply_fixes_after_configuration' _register_setup_function '_setup_adjust_state_permissions'
_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'
@ -161,6 +157,7 @@ function _register_functions() {
[[ ${ENABLE_CLAMAV} -eq 1 ]] && _register_start_daemon '_start_daemon_clamav' [[ ${ENABLE_CLAMAV} -eq 1 ]] && _register_start_daemon '_start_daemon_clamav'
[[ ${ENABLE_AMAVIS} -eq 1 ]] && _register_start_daemon '_start_daemon_amavis' [[ ${ENABLE_AMAVIS} -eq 1 ]] && _register_start_daemon '_start_daemon_amavis'
[[ ${ACCOUNT_PROVISIONER} == 'FILE' ]] && _register_start_daemon '_start_daemon_changedetector' [[ ${ACCOUNT_PROVISIONER} == 'FILE' ]] && _register_start_daemon '_start_daemon_changedetector'
[[ ${ENABLE_GETMAIL} -eq 1 ]] && _register_start_daemon '_start_daemon_getmail'
} }
# ------------------------------------------------------------ # ------------------------------------------------------------
@ -169,9 +166,6 @@ 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
@ -179,25 +173,35 @@ if [[ ! -f /CONTAINER_START ]]; then
_register_functions _register_functions
_check _check
_setup
_run_user_patches # 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 else
# container was restarted _setup
_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
date >/CONTAINER_START date >/CONTAINER_START
# Container logs will receive updates from this log file:
MAIN_LOGFILE=/var/log/mail/mail.log
# NOTE: rsyslogd would usually create this later during `_start_daemons`, however it would already exist if the container was restarted.
touch "${MAIN_LOGFILE}"
# Ensure `tail` follows the correct position of the log file for this container start (new logs begin once `_start_daemons` is called)
TAIL_START=$(( $(wc -l < "${MAIN_LOGFILE}") + 1 ))
[[ ${LOG_LEVEL} =~ (debug|trace) ]] && print-environment [[ ${LOG_LEVEL} =~ (debug|trace) ]] && print-environment
_start_daemons _start_daemons
# Container start-up scripts completed. `tail` will now pipe the log updates to stdout:
_log 'info' "${HOSTNAME} is up and running" _log 'info' "${HOSTNAME} is up and running"
exec tail -Fn "+${TAIL_START}" "${MAIN_LOGFILE}"
touch /var/log/mail/mail.log
exec tail -Fn 0 /var/log/mail/mail.log

View File

@ -26,24 +26,6 @@ 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

@ -34,6 +34,7 @@ function _start_daemon_clamav { _default_start_daemon 'clamav' ;
function _start_daemon_cron { _default_start_daemon 'cron' ; } function _start_daemon_cron { _default_start_daemon 'cron' ; }
function _start_daemon_dovecot { _default_start_daemon 'dovecot' ; } function _start_daemon_dovecot { _default_start_daemon 'dovecot' ; }
function _start_daemon_fail2ban { _default_start_daemon 'fail2ban' ; } function _start_daemon_fail2ban { _default_start_daemon 'fail2ban' ; }
function _start_daemon_getmail { _default_start_daemon 'getmail' ; }
function _start_daemon_opendkim { _default_start_daemon 'opendkim' ; } function _start_daemon_opendkim { _default_start_daemon 'opendkim' ; }
function _start_daemon_opendmarc { _default_start_daemon 'opendmarc' ; } function _start_daemon_opendmarc { _default_start_daemon 'opendmarc' ; }
function _start_daemon_postgrey { _default_start_daemon 'postgrey' ; } function _start_daemon_postgrey { _default_start_daemon 'postgrey' ; }

View File

@ -82,7 +82,9 @@ function _setup_timezone() {
fi fi
} }
function _setup_apply_fixes_after_configuration() { # Misc checks and fixes migrated here until next refactor:
# 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
@ -101,9 +103,11 @@ function _setup_apply_fixes_after_configuration() {
_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 _run_user_patches() { function _setup_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
@ -113,3 +117,32 @@ function _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,7 +23,11 @@ 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,13 +3,54 @@
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 pop3 (it will be eventually enabled later in the script, if requested) # Disable these protocols by default, they can be enabled later via ENV (ENABLE_POP3, ENABLE_IMAP, ENABLE_MANAGESIEVE)
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
@ -24,6 +65,7 @@ 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
@ -55,6 +97,12 @@ 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}
@ -192,8 +240,3 @@ 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

@ -4,38 +4,46 @@ function _setup_getmail() {
if [[ ${ENABLE_GETMAIL} -eq 1 ]]; then if [[ ${ENABLE_GETMAIL} -eq 1 ]]; then
_log 'trace' 'Preparing Getmail configuration' _log 'trace' 'Preparing Getmail configuration'
local GETMAILRC ID CONFIGS local GETMAIL_RC ID GETMAIL_DIR
GETMAILRC='/etc/getmailrc.d' local GETMAIL_CONFIG_DIR='/tmp/docker-mailserver/getmail'
CONFIGS=0 local GETMAIL_RC_DIR='/etc/getmailrc.d'
local GETMAIL_RC_GENERAL_CF="${GETMAIL_CONFIG_DIR}/getmailrc_general.cf"
local GETMAIL_RC_GENERAL='/etc/getmailrc_general'
mkdir -p "${GETMAILRC}" # Create the directory /etc/getmailrc.d to place the user config in later.
mkdir -p "${GETMAIL_RC_DIR}"
# Generate getmailrc configs, starting with the `/etc/getmailrc_general` base config, # Check if custom getmailrc_general.cf file is present.
# Add a unique `message_log` config, then append users own config to the end. if [[ -f "${GETMAIL_RC_GENERAL_CF}" ]]; then
for FILE in /tmp/docker-mailserver/getmail-*.cf; do _log 'debug' "Custom 'getmailrc_general.cf' found"
if [[ -f ${FILE} ]]; then cp "${GETMAIL_RC_GENERAL_CF}" "${GETMAIL_RC_GENERAL}"
CONFIGS=1 fi
ID=$(cut -d '-' -f 3 <<< "${FILE}" | cut -d '.' -f 1)
local GETMAIL_CONFIG="${GETMAILRC}/getmailrc-${ID}"
cat /etc/getmailrc_general >"${GETMAIL_CONFIG}" # If no matching filenames are found, and the shell option nullglob is disabled, the word is left unchanged.
echo -e "message_log = /var/log/mail/getmail-${ID}.log\n" >>"${GETMAIL_CONFIG}" # If the nullglob option is set, and no matches are found, the word is removed.
cat "${FILE}" >>"${GETMAIL_CONFIG}" shopt -s nullglob
# Generate getmailrc configs, starting with the `/etc/getmailrc_general` base config, then appending users own config to the end.
for FILE in "${GETMAIL_CONFIG_DIR}"/*.cf; do
if [[ ${FILE} =~ /getmail/(.+)\.cf ]] && [[ ${FILE} != "${GETMAIL_RC_GENERAL_CF}" ]]; then
ID=${BASH_REMATCH[1]}
_log 'debug' "Processing getmail config '${ID}'"
GETMAIL_RC=${GETMAIL_RC_DIR}/${ID}
cat "${GETMAIL_RC_GENERAL}" "${FILE}" >"${GETMAIL_RC}"
fi fi
done done
# Strip read access from non-root due to files containing secrets:
chmod -R 600 "${GETMAIL_RC_DIR}"
if [[ ${CONFIGS} -eq 1 ]]; then # Directory, where "oldmail" files are stored.
cat >/etc/cron.d/getmail << EOF # For more information see: https://getmail6.org/faq.html#faq-about-oldmail
*/${GETMAIL_POLL} * * * * root /usr/local/bin/getmail-cron # The debug command for getmail expects this location to exist.
EOF GETMAIL_DIR=/var/lib/getmail
chmod -R 600 "${GETMAILRC}" _log 'debug' "Creating getmail state-dir '${GETMAIL_DIR}'"
fi mkdir -p "${GETMAIL_DIR}"
# Both the debug command and cron job (that runs getmail) for getmail
# expect this location to exist.
GETMAILDIR=/tmp/docker-mailserver/getmail
mkdir -p "${GETMAILDIR}"
else else
_log 'debug' 'Getmail is disabled' _log 'debug' 'Getmail is disabled'
fi fi

View File

@ -1,15 +1,5 @@
#!/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,20 +1,24 @@
#!/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() {
local DEST DESTDIR STATEDIR SERVICEDIR SERVICEDIRS SERVICEFILE SERVICEFILES if [[ ! -d ${DMS_STATE_DIR} ]]; then
_log 'debug' "'${DMS_STATE_DIR}' is not present - not consolidating state"
return 0
fi
STATEDIR='/var/mail-state' _log 'debug' "Consolidating all state onto ${DMS_STATE_DIR}"
if [[ -d ${STATEDIR} ]]; then local DEST SERVICEDIR SERVICEDIRS SERVICEFILE SERVICEFILES
_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
@ -23,6 +27,7 @@ function _setup_save_states() {
[[ ${ENABLE_CLAMAV} -eq 1 ]] && SERVICEDIRS+=('lib/clamav') [[ ${ENABLE_CLAMAV} -eq 1 ]] && SERVICEDIRS+=('lib/clamav')
[[ ${ENABLE_FAIL2BAN} -eq 1 ]] && SERVICEDIRS+=('lib/fail2ban') [[ ${ENABLE_FAIL2BAN} -eq 1 ]] && SERVICEDIRS+=('lib/fail2ban')
[[ ${ENABLE_FETCHMAIL} -eq 1 ]] && SERVICEDIRS+=('lib/fetchmail') [[ ${ENABLE_FETCHMAIL} -eq 1 ]] && SERVICEDIRS+=('lib/fetchmail')
[[ ${ENABLE_GETMAIL} -eq 1 ]] && SERVICEDIRS+=('lib/getmail')
[[ ${ENABLE_MTA_STS} -eq 1 ]] && SERVICEDIRS+=('lib/mta-sts') [[ ${ENABLE_MTA_STS} -eq 1 ]] && SERVICEDIRS+=('lib/mta-sts')
[[ ${ENABLE_POSTGREY} -eq 1 ]] && SERVICEDIRS+=('lib/postgrey') [[ ${ENABLE_POSTGREY} -eq 1 ]] && SERVICEDIRS+=('lib/postgrey')
[[ ${ENABLE_RSPAMD} -eq 1 ]] && SERVICEDIRS+=('lib/rspamd') [[ ${ENABLE_RSPAMD} -eq 1 ]] && SERVICEDIRS+=('lib/rspamd')
@ -35,10 +40,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="${STATEDIR}/${SERVICEFILE}" DEST="${DMS_STATE_DIR}/${SERVICEFILE}"
DESTDIR="${DEST%/*}"
mkdir -p "${DESTDIR}" # Append service parent dir(s) path to the state dir and ensure it exists:
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:
@ -49,7 +54,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="${STATEDIR}" "${DEST}" 2>/dev/null || true chcon -R --reference="${DMS_STATE_DIR}" "${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
@ -58,7 +63,7 @@ function _setup_save_states() {
done done
for SERVICEDIR in "${SERVICEDIRS[@]}"; do for SERVICEDIR in "${SERVICEDIRS[@]}"; do
DEST="${STATEDIR}/${SERVICEDIR//\//-}" DEST="${DMS_STATE_DIR}/${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),
@ -73,33 +78,45 @@ 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:
chcon -R --reference="${STATEDIR}" "${DEST}" 2>/dev/null || true # https://github.com/docker-mailserver/docker-mailserver/pull/3890
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 assocaiated path in /var/mail-state/ ($DEST): # sourced from associated 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' "Fixing ${STATEDIR}/* permissions" _log 'trace' "Ensuring correct ownership + permissions for DMS state dir: '${DMS_STATE_DIR}'"
[[ ${ENABLE_AMAVIS} -eq 1 ]] && chown -R amavis:amavis "${STATEDIR}/lib-amavis" [[ ${ENABLE_AMAVIS} -eq 1 ]] && chown -R amavis:amavis "${DMS_STATE_DIR}/lib-amavis"
[[ ${ENABLE_CLAMAV} -eq 1 ]] && chown -R clamav:clamav "${STATEDIR}/lib-clamav" [[ ${ENABLE_CLAMAV} -eq 1 ]] && chown -R clamav:clamav "${DMS_STATE_DIR}/lib-clamav"
[[ ${ENABLE_FETCHMAIL} -eq 1 ]] && chown -R fetchmail:nogroup "${STATEDIR}/lib-fetchmail" [[ ${ENABLE_FETCHMAIL} -eq 1 ]] && chown -R fetchmail:nogroup "${DMS_STATE_DIR}/lib-fetchmail"
[[ ${ENABLE_MTA_STS} -eq 1 ]] && chown -R _mta-sts:_mta-sts "${STATEDIR}/lib-mta-sts" [[ ${ENABLE_MTA_STS} -eq 1 ]] && chown -R _mta-sts:_mta-sts "${DMS_STATE_DIR}/lib-mta-sts"
[[ ${ENABLE_POSTGREY} -eq 1 ]] && chown -R postgrey:postgrey "${STATEDIR}/lib-postgrey" [[ ${ENABLE_POSTGREY} -eq 1 ]] && chown -R postgrey:postgrey "${DMS_STATE_DIR}/lib-postgrey"
[[ ${ENABLE_RSPAMD} -eq 1 ]] && chown -R _rspamd:_rspamd "${STATEDIR}/lib-rspamd" [[ ${ENABLE_RSPAMD} -eq 1 ]] && chown -R _rspamd:_rspamd "${DMS_STATE_DIR}/lib-rspamd"
[[ ${ENABLE_RSPAMD_REDIS} -eq 1 ]] && chown -R redis:redis "${STATEDIR}/lib-redis" [[ ${ENABLE_RSPAMD_REDIS} -eq 1 ]] && chown -R redis:redis "${DMS_STATE_DIR}/lib-redis"
[[ ${ENABLE_SPAMASSASSIN} -eq 1 ]] && chown -R debian-spamd:debian-spamd "${STATEDIR}/lib-spamassassin" [[ ${ENABLE_SPAMASSASSIN} -eq 1 ]] && chown -R debian-spamd:debian-spamd "${DMS_STATE_DIR}/lib-spamassassin"
chown -R root:root "${STATEDIR}/lib-logrotate" chown -R root:root "${DMS_STATE_DIR}/lib-logrotate"
chown -R postfix:postfix "${STATEDIR}/lib-postfix" chown -R postfix:postfix "${DMS_STATE_DIR}/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
@ -108,17 +125,14 @@ function _setup_save_states() {
# 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 "${STATEDIR}/spool-postfix" chown -R postfix:root "${DMS_STATE_DIR}/spool-postfix"
chown root:root "${STATEDIR}/spool-postfix" chown root:root "${DMS_STATE_DIR}/spool-postfix"
# These two require the postdrop(103) group: # These two require the postdrop(103) group:
chgrp -R postdrop "${STATEDIR}"/spool-postfix/{maildrop,public} chgrp -R postdrop "${DMS_STATE_DIR}"/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 "${STATEDIR}/spool-postfix/maildrop" chmod 730 "${DMS_STATE_DIR}/spool-postfix/maildrop"
chmod 710 "${STATEDIR}/spool-postfix/public" chmod 710 "${DMS_STATE_DIR}/spool-postfix/public"
else
_log 'debug' "'${STATEDIR}' is not present; Not consolidating state"
fi
} }

View File

@ -79,6 +79,8 @@ EOF
if [[ ${ACCOUNT_PROVISIONER} == 'FILE' ]]; then if [[ ${ACCOUNT_PROVISIONER} == 'FILE' ]]; then
postconf 'virtual_mailbox_maps = texthash:/etc/postfix/vmailbox' postconf 'virtual_mailbox_maps = texthash:/etc/postfix/vmailbox'
fi fi
# Historical context regarding decision to use LMTP instead of LDA (do not change this):
# https://github.com/docker-mailserver/docker-mailserver/issues/4178#issuecomment-2375489302
postconf 'virtual_transport = lmtp:unix:/var/run/dovecot/lmtp' postconf 'virtual_transport = lmtp:unix:/var/run/dovecot/lmtp'
fi fi
@ -91,13 +93,19 @@ 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
sed -i -E 's|(smtpd_sender_restrictions =)|\1 check_sender_access texthash:/tmp/docker-mailserver/postfix-send-access.cf,|' /etc/postfix/main.cf # Prefer to prepend to our specialized variant instead:
# https://github.com/docker-mailserver/docker-mailserver/pull/4379
sed -i -E 's|^(dms_smtpd_sender_restrictions =)|\1 check_sender_access texthash:/tmp/docker-mailserver/postfix-send-access.cf,|' /etc/postfix/main.cf
fi 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'
@ -129,7 +137,7 @@ function __postfix__setup_override_configuration() {
# Do not directly output to 'main.cf' as this causes a read-write-conflict. # Do not directly output to 'main.cf' as this causes a read-write-conflict.
# `postconf` output is filtered to skip expected warnings regarding overrides: # `postconf` output is filtered to skip expected warnings regarding overrides:
# https://github.com/docker-mailserver/docker-mailserver/pull/3880#discussion_r1510414576 # https://github.com/docker-mailserver/docker-mailserver/pull/3880#discussion_r1510414576
postconf -n >/tmp/postfix-main-new.cf 2> >(grep -v 'overriding earlier entry') postconf -n >/tmp/postfix-main-new.cf 2> >(grep -v 'overriding earlier entry' >&2)
mv /tmp/postfix-main-new.cf /etc/postfix/main.cf mv /tmp/postfix-main-new.cf /etc/postfix/main.cf
_adjust_mtime_for_postfix_maincf _adjust_mtime_for_postfix_maincf

View File

@ -155,13 +155,6 @@ 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

@ -76,8 +76,9 @@ function __rspamd__run_early_setup_and_checks() {
mkdir -p /var/lib/rspamd/ mkdir -p /var/lib/rspamd/
: >/var/lib/rspamd/stats.ucl : >/var/lib/rspamd/stats.ucl
if [[ -d ${RSPAMD_DMS_OVERRIDE_D} ]]; then # Copy if directory exists and is not empty
cp "${RSPAMD_DMS_OVERRIDE_D}"/* "${RSPAMD_OVERRIDE_D}" if [[ -d ${RSPAMD_DMS_OVERRIDE_D} ]] && [[ -z $(find "${RSPAMD_DMS_OVERRIDE_D}" -maxdepth 0 -empty) ]]; then
cp "${RSPAMD_DMS_OVERRIDE_D}/"* "${RSPAMD_OVERRIDE_D}"
fi fi
if [[ ${ENABLE_AMAVIS} -eq 1 ]] || [[ ${ENABLE_SPAMASSASSIN} -eq 1 ]]; then if [[ ${ENABLE_AMAVIS} -eq 1 ]] || [[ ${ENABLE_SPAMASSASSIN} -eq 1 ]]; then
@ -319,8 +320,7 @@ function __rspamd__setup_check_authenticated() {
local MODULE_FILE="${RSPAMD_LOCAL_D}/settings.conf" local MODULE_FILE="${RSPAMD_LOCAL_D}/settings.conf"
readonly MODULE_FILE readonly MODULE_FILE
if _env_var_expect_zero_or_one 'RSPAMD_CHECK_AUTHENTICATED' \ if _env_var_expect_zero_or_one 'RSPAMD_CHECK_AUTHENTICATED' \
&& [[ ${RSPAMD_CHECK_AUTHENTICATED} -eq 0 ]] && [[ ${RSPAMD_CHECK_AUTHENTICATED} -eq 0 ]]; then
then
__rspamd__log 'debug' 'Content checks for authenticated users are disabled' __rspamd__log 'debug' 'Content checks for authenticated users are disabled'
else else
__rspamd__log 'debug' 'Enabling content checks for authenticated users' __rspamd__log 'debug' 'Enabling content checks for authenticated users'
@ -330,34 +330,24 @@ function __rspamd__setup_check_authenticated() {
fi fi
} }
# This function performs a simple check: go through DKIM configuration files, acquire # This function performs a simple check on the queried rspamd DKIM configuration:
# all private key file locations and check whether they exist and whether they can be # - Acquire all private key file locations and check whether they exist and can be accessed by Rspamd.
# accessed by Rspamd. # - We are not checking paths that contain the '$' symbol.
function __rspamd__check_dkim_permissions() { function __rspamd__check_dkim_permissions() {
local DKIM_CONF_FILES DKIM_KEY_FILES local KEY_FILE
[[ -f ${RSPAMD_LOCAL_D}/dkim_signing.conf ]] && DKIM_CONF_FILES+=("${RSPAMD_LOCAL_D}/dkim_signing.conf") while read -r KEY_FILE; do
[[ -f ${RSPAMD_OVERRIDE_D}/dkim_signing.conf ]] && DKIM_CONF_FILES+=("${RSPAMD_OVERRIDE_D}/dkim_signing.conf") if [[ -f ${KEY_FILE} ]]; then
__rspamd__log 'trace' "Checking DKIM file '${KEY_FILE}'"
# Here, we populate DKIM_KEY_FILES which we later iterate over. DKIM_KEY_FILES
# contains all keys files configured by the user.
local FILE
for FILE in "${DKIM_CONF_FILES[@]}"; do
readarray -t DKIM_KEY_FILES_TMP < <(grep -o -E 'path = .*' "${FILE}" | cut -d '=' -f 2 | tr -d ' ";')
DKIM_KEY_FILES+=("${DKIM_KEY_FILES_TMP[@]}")
done
for FILE in "${DKIM_KEY_FILES[@]}"; do
if [[ -f ${FILE} ]]; then
__rspamd__log 'trace' "Checking DKIM file '${FILE}'"
# See https://serverfault.com/a/829314 for an explanation on `-exec false {} +` # See https://serverfault.com/a/829314 for an explanation on `-exec false {} +`
# We additionally resolve symbolic links to check the permissions of the actual files # We additionally resolve symbolic links to check the permissions of the actual files
if find "$(realpath -eL "${FILE}")" \( -user _rspamd -or -group _rspamd -or -perm -o=r \) -exec false {} +; then if find "$(realpath -L "${KEY_FILE}")" \( -user _rspamd -or -group _rspamd -or -perm -o=r \) \
__rspamd__log 'warn' "Rspamd DKIM private key file '${FILE}' does not appear to have correct permissions/ownership for Rspamd to use it" -exec false {} +; then
__rspamd__log 'warn' "Rspamd DKIM private key file '${KEY_FILE}' does not appear to have correct permissions/ownership for Rspamd to use it"
else else
__rspamd__log 'trace' "DKIM file '${FILE}' permissions and ownership appear correct" __rspamd__log 'trace' "DKIM file '${KEY_FILE}' permissions and ownership appear correct"
fi fi
else else
__rspamd__log 'warn' "Rspamd DKIM private key file '${FILE}' is configured for usage, but does not appear to exist" __rspamd__log 'warn' "Rspamd DKIM private key file '${KEY_FILE}' is configured for usage, but does not appear to exist"
fi fi
done done < <(rspamadm configdump dkim_signing | grep 'path =' | grep -v -F '$' | awk '{print $3}' | tr -d ';"')
} }

View File

@ -4,9 +4,16 @@
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
@ -55,6 +62,8 @@ 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}"
@ -159,15 +168,27 @@ function __environment_variables_general_setup() {
VARS[UPDATE_CHECK_INTERVAL]="${UPDATE_CHECK_INTERVAL:=1d}" VARS[UPDATE_CHECK_INTERVAL]="${UPDATE_CHECK_INTERVAL:=1d}"
} }
function _environment_variables_oauth2() { function __environment_variables_log_level() {
_log 'debug' 'Setting OAUTH2-related environment variables now' 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}')"
VARS[OAUTH2_INTROSPECTION_URL]="${OAUTH2_INTROSPECTION_URL:=}" # shellcheck disable=SC2034
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:=}"
@ -177,19 +198,26 @@ 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'
# Only used by the supervisor service command (upstream default: `/etc/default/saslauthd`) # This ENV is only used by the supervisor service config `saslauth.conf`:
VARS[SASLAUTHD_MECHANISMS]="${SASLAUTHD_MECHANISMS:=pam}" # NOTE: `pam` is set as the upstream default in `/etc/default/saslauthd`
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

@ -170,3 +170,12 @@ stderr_logfile=/var/log/supervisor/%(program_name)s.log
command=/usr/bin/mta-sts-daemon --config /etc/mta-sts-daemon.yml command=/usr/bin/mta-sts-daemon --config /etc/mta-sts-daemon.yml
user=_mta-sts user=_mta-sts
environment=HOME=/var/lib/mta-sts environment=HOME=/var/lib/mta-sts
[program:getmail]
startsecs=0
stopwaitsecs=55
autostart=false
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
command=/bin/bash -l -c /usr/local/bin/getmail-service.sh

View File

@ -7,24 +7,6 @@ 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
@ -33,13 +15,3 @@ 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

@ -3,3 +3,13 @@ alias1@localhost.localdomain user1@localhost.localdomain
# this is also a test comment, :O # this is also a test comment, :O
alias2@localhost.localdomain external1@otherdomain.tld alias2@localhost.localdomain external1@otherdomain.tld
@localdomain2.com user1@localhost.localdomain @localdomain2.com user1@localhost.localdomain
## Dovecot "dummy accounts" for quota support (handled in `helpers/accounts.sh`)
# Do not filter alias by substring condition (longer prefix must be before substring alias):
# https://github.com/docker-mailserver/docker-mailserver/issues/2639
prefixtest@localhost.localdomain user2@otherdomain.tld
test@localhost.localdomain user2@otherdomain.tld
# Do not filter alias when input be treated as regex tokens (eg `.`):
# https://github.com/docker-mailserver/docker-mailserver/issues/4170
first-name@localhost.localdomain user2@otherdomain.tld
first.name@localhost.localdomain user2@otherdomain.tld

View File

@ -5,7 +5,7 @@
# #
# We do not use `custom-commands.conf` because this a feature # We do not use `custom-commands.conf` because this a feature
# we are testing too. # we are testing too.
echo "enable_test_patterns = true;" >>/etc/rspamd/local.d/options.inc echo 'gtube_patterns = "all"' >>/etc/rspamd/local.d/options.inc
# We want Dovecot to be very detailed about what it is doing, # We want Dovecot to be very detailed about what it is doing,
# specifically for Sieve because we need to check whether the # specifically for Sieve because we need to check whether the

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+set} ]]; then elif [[ -n ${CONTAINER_NAME:-} ]]; 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 [[ ${!1+set} != 'set' ]]; then if [[ -z ${!1:-} ]]; then
echo "ERROR: (helper/setup.sh) '${1:?No variable name given to __check_if_set}' is not set" >&2 echo "ERROR: (helper/setup.sh) '${1:?No variable name given to __check_if_set}' is not set" >&2
exit 1 exit 1
fi fi

Some files were not shown because too many files have changed in this diff Show More