first commit
This commit is contained in:
commit
1910e01da1
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
testdata/
|
||||||
|
cmd/maddy/maddy
|
||||||
|
maddy
|
||||||
|
tests/maddy.cover
|
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.{scd,go}]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 2
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
52
.github/CODE_OF_CONDUCT.md
vendored
Normal file
52
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Code of Merit
|
||||||
|
|
||||||
|
**1.** The project creators, lead developers, core team, constitute the managing
|
||||||
|
members of the project and have final say in every decision of the project,
|
||||||
|
technical or otherwise, including overruling previous decisions. There are no
|
||||||
|
limitations to this decisional power.
|
||||||
|
|
||||||
|
**2.** Contributions are an expected result of your membership on the project.
|
||||||
|
Don’t expect others to do your work or help you with your work forever.
|
||||||
|
|
||||||
|
**3.** All members have the same opportunities to seek any challenge they want
|
||||||
|
within the project.
|
||||||
|
|
||||||
|
**4.** Authority or position in the project will be proportional to the accrued
|
||||||
|
contribution. Seniority must be earned.
|
||||||
|
|
||||||
|
**5.** Software is evolutive: the better implementations must supersede lesser
|
||||||
|
implementations. Technical advantage is the primary evaluation metric.
|
||||||
|
|
||||||
|
**6.** This is a space for technical prowess; topics outside of the project will
|
||||||
|
not be tolerated.
|
||||||
|
|
||||||
|
**7.** Non technical conflicts will be discussed in a separate space. Disruption
|
||||||
|
of the project will not be allowed.
|
||||||
|
|
||||||
|
**8.** Individual characteristics, including but not limited to, body, sex,
|
||||||
|
sexual preference, race, language, religion, nationality, or political
|
||||||
|
preferences are irrelevant in the scope of the project and will not be taken
|
||||||
|
into account concerning your value or that of your contribution to the project.
|
||||||
|
|
||||||
|
**9.** Discuss or debate the idea, not the person.
|
||||||
|
|
||||||
|
**10.** There is no room for ambiguity: Ambiguity will be met with questioning;
|
||||||
|
further ambiguity will be met with silence. It is the responsibility of the
|
||||||
|
originator to provide requested context.
|
||||||
|
|
||||||
|
**11.** If something is illegal outside the scope of the project, it is illegal
|
||||||
|
in the scope of the project. This Code of Merit does not take precedence over
|
||||||
|
governing law.
|
||||||
|
|
||||||
|
**12.** This Code of Merit governs the technical procedures of the project not
|
||||||
|
the activities outside of it.
|
||||||
|
|
||||||
|
**13.** Participation on the project equates to agreement of this Code of Merit.
|
||||||
|
|
||||||
|
**14.** No objectives beyond the stated objectives of this project are relevant
|
||||||
|
to the project. Any intent to deviate the project from its original purpose of
|
||||||
|
existence will constitute grounds for remedial action which may include
|
||||||
|
expulsion from the project.
|
||||||
|
|
||||||
|
This document is the Code of Merit
|
||||||
|
(<strike>`http://code-of-merit.org`</strike>), version 1.0.
|
59
.github/CONTRIBUTING.md
vendored
Normal file
59
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Contributing Guidelines
|
||||||
|
|
||||||
|
Of course, we love our contributors. Thanks for spending time on making maddy
|
||||||
|
better.
|
||||||
|
|
||||||
|
## Reporting bugs
|
||||||
|
|
||||||
|
**Issue tracker is meant to be used only if you have a problem or a feature
|
||||||
|
request. If you just have some questions about maddy - prefer to use the
|
||||||
|
[IRC channel](https://webchat.oftc.net/?channels=maddy&uio=MT11bmRlZmluZWQb1).**
|
||||||
|
|
||||||
|
- Provide log files, preferably with 'debug' directive set.
|
||||||
|
- Provide the exact steps to reproduce the issue.
|
||||||
|
- Provide the example message that causes the error, if applicable.
|
||||||
|
- "Too much information is better than not enough information".
|
||||||
|
|
||||||
|
Issues without enough information will be ignored and possibly closed.
|
||||||
|
Take some time to be more useful.
|
||||||
|
|
||||||
|
See SECURITY.md for information on how to report vulnerabilities.
|
||||||
|
|
||||||
|
## Contributing Code
|
||||||
|
|
||||||
|
0. Use common sense.
|
||||||
|
1. Learn Git. Especially, what is `git rebase`. We may ask you to use it if
|
||||||
|
needed.
|
||||||
|
2. Tell us that you are willing to work on an issue.
|
||||||
|
3. Fork the repo. Create a new branch based on `dev`, write your code. Open a
|
||||||
|
PR.
|
||||||
|
|
||||||
|
Ask for advice if you are not sure. We don't bite.
|
||||||
|
|
||||||
|
maddy design summary and some recommendations are provided in
|
||||||
|
[HACKING.md](../HACKING.md) file.
|
||||||
|
|
||||||
|
## Commits
|
||||||
|
|
||||||
|
1. Prefix commit message with a package path if it affects only a single
|
||||||
|
package. Omit `internal/` for brevity.
|
||||||
|
2. Provide reasoning for details in the source code itself (via comments),
|
||||||
|
provide reasoning for high-level decisions in the commit message.
|
||||||
|
3. Make sure every commit builds & passes tests. Otherwise `git bisect` becomes
|
||||||
|
unusable.
|
||||||
|
|
||||||
|
## Git workflow
|
||||||
|
|
||||||
|
`dev` branch includes the in-development version for the next feature release.
|
||||||
|
It is based on commit of the latest stable release and is merged into `master`
|
||||||
|
on release via fast-forward. Unlike `master`, `dev` **is not a protected branch
|
||||||
|
and may get force-pushes**.
|
||||||
|
|
||||||
|
`master` branch contains the latest stable release and is frozen between
|
||||||
|
releases.
|
||||||
|
|
||||||
|
`fix-X.Y` are temporary branches containing backported security fixes.
|
||||||
|
They are based on the commit of the corresponding stable release and exist
|
||||||
|
while the corresponding release is maintained. A `fix-*` branch is not created
|
||||||
|
for the latest release. Changes are added to these branches by cherry-picking
|
||||||
|
needed commits from the `dev` branch.
|
27
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: If you think something is broken
|
||||||
|
title: Bug report
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Describe the bug
|
||||||
|
|
||||||
|
What do you think is wrong?
|
||||||
|
|
||||||
|
# Steps to reproduce
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
|
||||||
|
Use a service like hastebin.com or attach a file if it is big
|
||||||
|
|
||||||
|
# Configuration file
|
||||||
|
|
||||||
|
Located in /etc/maddy/maddy.conf by default, don't forget to remove DB passwords
|
||||||
|
and other security-related stuff.
|
||||||
|
|
||||||
|
# Environment information
|
||||||
|
|
||||||
|
* maddy version: ?
|
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
contact_links:
|
||||||
|
- name: Questions
|
||||||
|
url: "https://github.com/foxcpp/maddy/discussions/new?category=q-a"
|
||||||
|
about: "Use GitHub discussions for any questions"
|
||||||
|
- name: IRC channel
|
||||||
|
url: "https://webchat.oftc.net/?channels=maddy&uio=MT11bmRlZmluZWQb1"
|
||||||
|
about: "... or there is also an IRC channel for any discussions"
|
22
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: If you would like to see a new feature in maddy.
|
||||||
|
title: Feature request
|
||||||
|
labels: new feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Use case
|
||||||
|
|
||||||
|
What problem you are trying to solve?
|
||||||
|
|
||||||
|
Note alternatives you considered and why they are not useful.
|
||||||
|
|
||||||
|
# Your idea for a solution
|
||||||
|
|
||||||
|
How your solution would work in general?
|
||||||
|
Note that some overly complicated solutions may be rejected because maddy is
|
||||||
|
meant to be simple.
|
||||||
|
|
||||||
|
- [ ] I'm willing to help with the implementation
|
17
.github/SECURITY.md
vendored
Normal file
17
.github/SECURITY.md
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Two latest incompatible releases (e.g. 2.0.0 and 1.9.0).
|
||||||
|
|
||||||
|
Latest release gets all bug fixes, features, etc. Previous incompatible release
|
||||||
|
gets security fixes and fixes for problems that render software completely
|
||||||
|
unusable in certain configurations with no workaround.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you believe the vulnerabilitiy does have a big impact on existing
|
||||||
|
deployments - email `fox.cpp at disroot.org`, put "[maddy security]" in the
|
||||||
|
Subject.
|
||||||
|
|
||||||
|
Otherwise, open a public issue.
|
41
.github/releases.md
vendored
Normal file
41
.github/releases.md
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Release preparation
|
||||||
|
|
||||||
|
1. Run linters, fix all simple warnings. If the behavior is intentional - add
|
||||||
|
`nolint` comment and explanation. If the warning is non-trviail to fix - open
|
||||||
|
an issue.
|
||||||
|
```
|
||||||
|
golangci-lint run
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run unit tests suite. Verify that all disabled tests are not related to
|
||||||
|
serious problems and have corresponding issue open.
|
||||||
|
```
|
||||||
|
go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run integration tests suite. Verify that all disabled tests are not related
|
||||||
|
to serious problems and have corresponding issue open.
|
||||||
|
```
|
||||||
|
cd tests/
|
||||||
|
./run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Write release notes.
|
||||||
|
|
||||||
|
5. Create PGP-signed Git tag and push it to GitHub (do not create a "release"
|
||||||
|
yet).
|
||||||
|
|
||||||
|
5. Use environment configuration from maddy-repro bundle
|
||||||
|
(https://foxcpp.dev/maddy-repro) to build release artifacts.
|
||||||
|
|
||||||
|
6. Create detached PGP signatures for artifacts using key
|
||||||
|
3197BBD95137E682A59717B434BB2007081396F4.
|
||||||
|
|
||||||
|
7. Create sha256sums file for artifacts.
|
||||||
|
|
||||||
|
8. Create release on GitHub using the same text for
|
||||||
|
release notes. Attach signed artifacts and sha256sums file.
|
||||||
|
|
||||||
|
9. Build the Docker container and push it to hub.docker.com.
|
||||||
|
|
||||||
|
10. Post a message on the sr.ht mailing list.
|
158
.github/workflows/release.yml
vendored
Normal file
158
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
name: "Prepare release artifacts"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: [ "v*" ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
attestations: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
artifact-builder-x86:
|
||||||
|
name: "Prepare release artifacts (x86)"
|
||||||
|
if: github.ref_type == 'tag'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: "alpine:edge"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1 # v2 does not work with containers
|
||||||
|
- name: "Install build dependencies"
|
||||||
|
run: |
|
||||||
|
apk add --no-cache gcc go zstd
|
||||||
|
- name: "Create and package build tree"
|
||||||
|
run: |
|
||||||
|
./build.sh --builddir ~/package-output/ --static build
|
||||||
|
ver=$(cat .version)
|
||||||
|
if [ "v$ver" != "${{github.ref_name}}" ]; then echo ".version does not match the Git tag"; exit 1; fi
|
||||||
|
mv ~/package-output/ ~/maddy-$ver-x86_64-linux-musl
|
||||||
|
cd ~
|
||||||
|
tar c ./maddy-$ver-x86_64-linux-musl | zstd > ~/maddy-x86_64-linux-musl.tar.zst
|
||||||
|
cd -
|
||||||
|
- name: "Save source tree"
|
||||||
|
run: |
|
||||||
|
rm -rf .git
|
||||||
|
ver=$(cat .version)
|
||||||
|
cp -r . ~/maddy-$ver-src
|
||||||
|
cd ~
|
||||||
|
tar c ./maddy-$ver-src | zstd > ~/maddy-src.tar.zst
|
||||||
|
cd -
|
||||||
|
- name: "Upload source tree"
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: maddy-src.tar.zst
|
||||||
|
path: '~/maddy-src.tar.zst'
|
||||||
|
if-no-files-found: error
|
||||||
|
- name: "Upload binary tree"
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: maddy-binary.tar.zst
|
||||||
|
path: '~/maddy-x86_64-linux-musl.tar.zst'
|
||||||
|
if-no-files-found: error
|
||||||
|
- name: "Generate artifact attestation"
|
||||||
|
uses: actions/attest-build-provenance@v2
|
||||||
|
with:
|
||||||
|
subject-path: '~/maddy-x86_64-linux-musl.tar.zst'
|
||||||
|
artifact-builder-arm:
|
||||||
|
name: "Prepare release artifacts (aarch64)"
|
||||||
|
if: github.ref_type == 'tag'
|
||||||
|
runs-on: ubuntu-22.04-arm
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
# Building in a Docker container is a workaround for the issue of
|
||||||
|
# JavaScript-based GitHub Actions not being supported in Alpine
|
||||||
|
# containers on the Arm64 platform. Otherwise, we could completely reuse
|
||||||
|
# artifact-builder-x86 as a matrix job by running it on an Arm runner.
|
||||||
|
- name: Build in Docker container
|
||||||
|
run: |
|
||||||
|
# Create Dockerfile for the build
|
||||||
|
cat > Dockerfile << 'EOF'
|
||||||
|
FROM alpine:edge
|
||||||
|
RUN apk add --no-cache gcc go zstd musl-dev scdoc
|
||||||
|
WORKDIR /build
|
||||||
|
COPY . .
|
||||||
|
RUN ./build.sh --builddir /package-output/ --static build && \
|
||||||
|
ver=$(cat .version) && \
|
||||||
|
if [ "v$ver" != "${{github.ref_name}}" ]; then echo ".version does not match the Git tag"; exit 1; fi && \
|
||||||
|
mv /package-output/ /maddy-$ver-aarch64-linux-musl && \
|
||||||
|
cd / && \
|
||||||
|
tar c ./maddy-$ver-aarch64-linux-musl | zstd > /maddy-aarch64-linux-musl.tar.zst
|
||||||
|
EOF
|
||||||
|
# Build the image, create a temporary container and copy the artifact.
|
||||||
|
docker build -t maddy-builder .
|
||||||
|
container_id=$(docker create maddy-builder)
|
||||||
|
docker cp $container_id:/maddy-aarch64-linux-musl.tar.zst .
|
||||||
|
docker rm $container_id
|
||||||
|
- name: Upload binary tree
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: maddy-binary-aarch64.tar.zst
|
||||||
|
path: maddy-aarch64-linux-musl.tar.zst
|
||||||
|
if-no-files-found: error
|
||||||
|
- name: "Generate artifact attestation"
|
||||||
|
uses: actions/attest-build-provenance@v2
|
||||||
|
with:
|
||||||
|
subject-path: 'maddy-aarch64-linux-musl.tar.zst'
|
||||||
|
docker-builder:
|
||||||
|
name: "Build & push Docker image"
|
||||||
|
if: github.ref_type == 'tag'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: "Set up QEMU"
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
- name: "Set up Docker Buildx"
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: "Login to Docker Hub"
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
logout: false
|
||||||
|
- name: "Login to GitHub Container Registry"
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: "ghcr.io"
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
logout: false # https://news.ycombinator.com/item?id=28607735
|
||||||
|
- name: "Generate container metadata"
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
id: meta
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
foxcpp/maddy
|
||||||
|
ghcr.io/foxcpp/maddy
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.title=Maddy Mail Server
|
||||||
|
org.opencontainers.image.documentation=https://maddy.email/docker/
|
||||||
|
org.opencontainers.image.url=https://maddy.email
|
||||||
|
- name: "Build and push"
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
id: docker
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64 #,linux/arm64 Temporary disabled due to SIGSEGV in gcc.
|
||||||
|
file: Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
- name: "Generate container attestation"
|
||||||
|
uses: actions/attest-build-provenance@v2
|
||||||
|
with:
|
||||||
|
subject-name: ghcr.io/foxcpp/maddy
|
||||||
|
subject-digest: ${{ steps.docker.outputs.digest }}
|
||||||
|
push-to-registry: true
|
||||||
|
|
75
.github/workflows/test.yml
vendored
Normal file
75
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
name: "Testing"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master, dev ]
|
||||||
|
tags: [ "v*" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master, dev ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
checks: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
golangci:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
- name: "Install libpam"
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libpam-dev
|
||||||
|
- uses: golangci/golangci-lint-action@v6
|
||||||
|
with:
|
||||||
|
version: v1.60
|
||||||
|
args: "--timeout=30m"
|
||||||
|
buildsh:
|
||||||
|
name: "Verify build.sh"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
- name: "Install libpam"
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libpam-dev
|
||||||
|
- name: "Verify build.sh"
|
||||||
|
run: |
|
||||||
|
./build.sh
|
||||||
|
./build.sh --destdir destdir/ install
|
||||||
|
find destdir/
|
||||||
|
test:
|
||||||
|
name: "Build and test"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
- name: "Install libpam"
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libpam-dev
|
||||||
|
- name: "Unit & module tests"
|
||||||
|
run: |
|
||||||
|
go test ./... -coverprofile=coverage.out -covermode=atomic
|
||||||
|
- name: "Integration tests"
|
||||||
|
run: |
|
||||||
|
cd tests/
|
||||||
|
./run.sh
|
||||||
|
- uses: codecov/codecov-action@v2
|
||||||
|
with:
|
||||||
|
files: ./coverage.out
|
||||||
|
flags: unit
|
||||||
|
- uses: codecov/codecov-action@v2
|
||||||
|
with:
|
||||||
|
files: ./tests/coverage.out
|
||||||
|
flags: integration
|
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# gitignore.io
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
_testmain.go
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
**/.envrc
|
||||||
|
**/.DS_Store
|
||||||
|
|
||||||
|
# Tests coverage
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Compiled binaries
|
||||||
|
cmd/maddy/maddy
|
||||||
|
cmd/maddy-*-helper/maddy-*-helper
|
||||||
|
/maddy
|
||||||
|
|
||||||
|
# Man pages
|
||||||
|
docs/man/*.1
|
||||||
|
docs/man/*.5
|
||||||
|
|
||||||
|
# Certificates and private keys.
|
||||||
|
*.pem
|
||||||
|
*.crt
|
||||||
|
*.key
|
||||||
|
|
||||||
|
# Some directories that may be created during test-runs
|
||||||
|
# in repo directory.
|
||||||
|
cmd/maddy/*mtasts-cache
|
||||||
|
cmd/maddy/*queue
|
||||||
|
|
||||||
|
build/
|
||||||
|
|
||||||
|
tests/maddy.cover
|
||||||
|
tests/maddy
|
||||||
|
|
||||||
|
.idea/
|
17
.golangci.yml
Normal file
17
.golangci.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- gosimple
|
||||||
|
- errcheck
|
||||||
|
- staticcheck
|
||||||
|
- ineffassign
|
||||||
|
- typecheck
|
||||||
|
- govet
|
||||||
|
- unused
|
||||||
|
- goimports
|
||||||
|
- prealloc
|
||||||
|
- unconvert
|
||||||
|
- misspell
|
||||||
|
- whitespace
|
||||||
|
- nakedret
|
||||||
|
- dogsled
|
||||||
|
- copyloopvar
|
84
.mkdocs.yml
Normal file
84
.mkdocs.yml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
site_name: maddy
|
||||||
|
|
||||||
|
repo_url: https://github.com/foxcpp/maddy
|
||||||
|
|
||||||
|
theme: alb
|
||||||
|
|
||||||
|
markdown_extensions:
|
||||||
|
- codehilite:
|
||||||
|
guess_lang: false
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- faq.md
|
||||||
|
- Tutorials:
|
||||||
|
- tutorials/setting-up.md
|
||||||
|
- tutorials/building-from-source.md
|
||||||
|
- tutorials/alias-to-remote.md
|
||||||
|
- tutorials/pam.md
|
||||||
|
- Release builds: 'https://maddy.email/builds/'
|
||||||
|
- multiple-domains.md
|
||||||
|
- upgrading.md
|
||||||
|
- seclevels.md
|
||||||
|
- docker.md
|
||||||
|
- Reference manual:
|
||||||
|
- reference/modules.md
|
||||||
|
- reference/global-config.md
|
||||||
|
- reference/tls.md
|
||||||
|
- reference/tls-acme.md
|
||||||
|
- Endpoints configuration:
|
||||||
|
- reference/endpoints/imap.md
|
||||||
|
- reference/endpoints/smtp.md
|
||||||
|
- reference/endpoints/openmetrics.md
|
||||||
|
- IMAP storage:
|
||||||
|
- reference/storage/imap-filters.md
|
||||||
|
- reference/storage/imapsql.md
|
||||||
|
- Blob storage:
|
||||||
|
- reference/blob/fs.md
|
||||||
|
- reference/blob/s3.md
|
||||||
|
- reference/smtp-pipeline.md
|
||||||
|
- SMTP targets:
|
||||||
|
- reference/targets/queue.md
|
||||||
|
- reference/targets/remote.md
|
||||||
|
- reference/targets/smtp.md
|
||||||
|
- SMTP checks:
|
||||||
|
- reference/checks/actions.md
|
||||||
|
- reference/checks/dkim.md
|
||||||
|
- reference/checks/spf.md
|
||||||
|
- reference/checks/milter.md
|
||||||
|
- reference/checks/rspamd.md
|
||||||
|
- reference/checks/dnsbl.md
|
||||||
|
- reference/checks/command.md
|
||||||
|
- reference/checks/authorize_sender.md
|
||||||
|
- reference/checks/misc.md
|
||||||
|
- SMTP modifiers:
|
||||||
|
- reference/modifiers/dkim.md
|
||||||
|
- reference/modifiers/envelope.md
|
||||||
|
- Lookup tables (string translation):
|
||||||
|
- reference/table/static.md
|
||||||
|
- reference/table/regexp.md
|
||||||
|
- reference/table/file.md
|
||||||
|
- reference/table/sql_query.md
|
||||||
|
- reference/table/chain.md
|
||||||
|
- reference/table/email_localpart.md
|
||||||
|
- reference/table/email_with_domain.md
|
||||||
|
- reference/table/auth.md
|
||||||
|
- Authentication providers:
|
||||||
|
- reference/auth/pass_table.md
|
||||||
|
- reference/auth/pam.md
|
||||||
|
- reference/auth/shadow.md
|
||||||
|
- reference/auth/external.md
|
||||||
|
- reference/auth/ldap.md
|
||||||
|
- reference/auth/dovecot_sasl.md
|
||||||
|
- reference/auth/plain_separate.md
|
||||||
|
- reference/auth/netauth.md
|
||||||
|
- reference/config-syntax.md
|
||||||
|
- Integration with software:
|
||||||
|
- third-party/dovecot.md
|
||||||
|
- third-party/smtp-servers.md
|
||||||
|
- third-party/rspamd.md
|
||||||
|
- third-party/mailman3.md
|
||||||
|
- Internals:
|
||||||
|
- internals/specifications.md
|
||||||
|
- internals/unicode.md
|
||||||
|
- internals/quirks.md
|
||||||
|
- internals/sqlite.md
|
674
COPYING
Normal file
674
COPYING
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
32
Dockerfile
Normal file
32
Dockerfile
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
FROM golang:1.23-alpine AS build-env
|
||||||
|
|
||||||
|
ARG ADDITIONAL_BUILD_TAGS=""
|
||||||
|
|
||||||
|
RUN set -ex && \
|
||||||
|
apk upgrade --no-cache --available && \
|
||||||
|
apk add --no-cache build-base
|
||||||
|
|
||||||
|
WORKDIR /maddy
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . ./
|
||||||
|
RUN mkdir -p /pkg/data && \
|
||||||
|
cp maddy.conf.docker /pkg/data/maddy.conf && \
|
||||||
|
./build.sh --builddir /tmp --destdir /pkg/ --tags "docker ${ADDITIONAL_BUILD_TAGS}" build install
|
||||||
|
|
||||||
|
FROM alpine:3.21.2
|
||||||
|
LABEL maintainer="fox.cpp@disroot.org"
|
||||||
|
LABEL org.opencontainers.image.source=https://github.com/foxcpp/maddy
|
||||||
|
|
||||||
|
RUN set -ex && \
|
||||||
|
apk upgrade --no-cache --available && \
|
||||||
|
apk --no-cache add ca-certificates
|
||||||
|
COPY --from=build-env /pkg/data/maddy.conf /data/maddy.conf
|
||||||
|
COPY --from=build-env /pkg/usr/local/bin/maddy /bin/
|
||||||
|
|
||||||
|
EXPOSE 25 143 993 587 465
|
||||||
|
VOLUME ["/data"]
|
||||||
|
ENTRYPOINT ["/bin/maddy", "-config", "/data/maddy.conf"]
|
||||||
|
CMD ["run"]
|
130
HACKING.md
Normal file
130
HACKING.md
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
## Design goals
|
||||||
|
|
||||||
|
- **Make it easy to deploy.**
|
||||||
|
Minimal configuration changes should be required to get a typical mail server
|
||||||
|
running. Though, it is important to avoid making guesses for a
|
||||||
|
"zero-configuration". A wrong guess is worse than no guess.
|
||||||
|
|
||||||
|
- **Provide 80% of needed components.**
|
||||||
|
E-mail has evolved into a huge mess. With a single package to do one thing, it
|
||||||
|
quickly turns into a maintenance nightmare. Put all stuff mail server
|
||||||
|
typically needs into a single package. Though, leave controversial or highly
|
||||||
|
opinionated stuff out, don't force people to do things our way
|
||||||
|
(see next point).
|
||||||
|
|
||||||
|
- **Interoperate with existing software.**
|
||||||
|
Implement (de-facto) standard protocols not only for clients but also for
|
||||||
|
various server-side helper software (content filters, etc).
|
||||||
|
|
||||||
|
- **Be secure but interoperable.**
|
||||||
|
Verify DKIM signatures by default, use DMRAC policies by default, etc. This
|
||||||
|
makes default setup as secure as possible while maintaining reasonable
|
||||||
|
interoperability. Though, users can configure maddy to be stricter.
|
||||||
|
|
||||||
|
- **Achieve flexibility through composability.**
|
||||||
|
Allow connecting components in arbitrary ways instead of restricting users to
|
||||||
|
predefined templates.
|
||||||
|
|
||||||
|
- **Use Go concurrency features to the full extent.**
|
||||||
|
Do as much I/O as possible in parallel to minimize latencies. It is silly to
|
||||||
|
not do so when it is possible.
|
||||||
|
|
||||||
|
## Design summary
|
||||||
|
|
||||||
|
Here is a summary of how things are organized in maddy in general. It explains
|
||||||
|
things from the developer perspective and is meant to be used as an
|
||||||
|
introduction by the new developers/contributors. It is recommended to read
|
||||||
|
user documentation to understand how things work from the user perspective as
|
||||||
|
well.
|
||||||
|
|
||||||
|
- User documentation: [maddy.conf(5)](docs/man/maddy.5.scd)
|
||||||
|
- Design rationale: [Comments on design (Wiki)][1]
|
||||||
|
|
||||||
|
There are components called "modules". They are represented by objects
|
||||||
|
implementing the module.Module interface. Each module gets its unique name.
|
||||||
|
The function used to create a module instance is saved with this name as a key
|
||||||
|
into the global map called "modules registry".
|
||||||
|
|
||||||
|
Whenever module needs another module for some functionality, it references it
|
||||||
|
using a configuration directive with a matcher that internally calls
|
||||||
|
`modconfig.ModuleFromNode`. That function looks up the module "constructor" in
|
||||||
|
the registry, calls it with corresponding arguments, checks whether the
|
||||||
|
returned module satisfies the needed interfaces and then initializes it.
|
||||||
|
|
||||||
|
Alternatively, if configuration uses &-syntax to reference existing
|
||||||
|
configuration block, `ModuleFromNode` simply looks it up in the global instances
|
||||||
|
registry. All modules defined the configuration as a separate top-level blocks
|
||||||
|
are created before main initialization and are placed in the instances registry
|
||||||
|
where they can be looked up as mentioned before.
|
||||||
|
|
||||||
|
Top-level defined module instances are initialized (`Init` method) lazily as
|
||||||
|
they are required by other modules. 'smtp' and 'imap' modules follow a special
|
||||||
|
initialization path, so they are always initialized directly.
|
||||||
|
|
||||||
|
## Error handling
|
||||||
|
|
||||||
|
Familiarize yourself with the `github.com/foxcpp/maddy/framework/exterrors`
|
||||||
|
package and make sure you have the following for returned errors:
|
||||||
|
- SMTP status information (smtp\_code, smtp\_enchcode, smtp\_msg fields)
|
||||||
|
- SMTP message text should contain a generic description of the error
|
||||||
|
condition without any details to prevent accidental disclosure of the
|
||||||
|
server configuration details.
|
||||||
|
- `Temporary() == true` for temporary errors (see `exterrors.WithTemporary`)
|
||||||
|
- Field that includes the module name
|
||||||
|
|
||||||
|
The easiest way to get all of these is to use `exterrors.SMTPError`.
|
||||||
|
Put the original error into the `Err` field, so it can be inspected using
|
||||||
|
`errors.Is`, `errors.Unwrap`, etc. Put the module name into `CheckName` or
|
||||||
|
`TargetName`. Add any additional context information using the `Misc` field.
|
||||||
|
Note, the SMTP status code overrides the result of `exterrors.IsTemporary()`
|
||||||
|
for that error object, so set it using `exterrors.SMTPCode` that uses
|
||||||
|
`IsTemporary` to select between two codes.
|
||||||
|
|
||||||
|
If the error you are wrapping contains details in its structure fields (like
|
||||||
|
`*net.OpError`) - copy these values into `Misc` map, put the underlying error
|
||||||
|
object (`net.OpError.Err`, for example) into the `Err` field.
|
||||||
|
Avoid using `Reason` unless you are sure you can provide the error message
|
||||||
|
better than the `Err.Error()` or `Err` is `nil`.
|
||||||
|
|
||||||
|
Do not attempt to add a SMTP status information for every single possible
|
||||||
|
error. Use `exterrors.WithFields` with basic information for errors you don't
|
||||||
|
expect. The SMTP client will get the "Internal server error" message and this
|
||||||
|
is generally the right thing to do on unexpected errors.
|
||||||
|
|
||||||
|
### Goroutines and panics
|
||||||
|
|
||||||
|
If you start any goroutines - make sure to catch panics to make sure severe
|
||||||
|
bugs will not bring the whole server down.
|
||||||
|
|
||||||
|
## Adding a check
|
||||||
|
|
||||||
|
"Check" is a module that inspects the message and flags it as spam or rejects
|
||||||
|
it altogether based on some condition.
|
||||||
|
|
||||||
|
The skeleton for the stateful check module can be found in
|
||||||
|
`internal/check/skeleton.go`. Throw it into a file in
|
||||||
|
`internal/check/check_name` directory and start ~~breaking~~ extending it.
|
||||||
|
|
||||||
|
If you don't need any per-message state, you can use `StatelessCheck` wrapper.
|
||||||
|
See `check/dns` directory for a working example.
|
||||||
|
|
||||||
|
Here are some guidelines to make sure your check works well:
|
||||||
|
- RTFM, docs will tell you about any caveats.
|
||||||
|
- Don't share any state _between_ messages, your code will be executed in
|
||||||
|
parallel.
|
||||||
|
- Use `github.com/foxcpp/maddy/check.FailAction` to select behavior on check
|
||||||
|
failures. See other checks for examples on how to use it.
|
||||||
|
- You can assume that order of check functions execution is as follows:
|
||||||
|
`CheckConnection`, `CheckSender`, `CheckRcpt`, `CheckBody`.
|
||||||
|
|
||||||
|
## Adding a modifier
|
||||||
|
|
||||||
|
"Modifier" is a module that can modify some parts of the message data.
|
||||||
|
|
||||||
|
Note, currently this is not possible to modify the body contents, only header
|
||||||
|
can be modified.
|
||||||
|
|
||||||
|
Structure of the modifier implementation is similar to the structure of check
|
||||||
|
implementation, check `modify/replace\_addr.go` for a working example.
|
||||||
|
|
||||||
|
[1]: https://github.com/foxcpp/maddy/wiki/Dev:-Comments-on-design
|
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Maddy Mail Server
|
||||||
|
=====================
|
||||||
|
> Composable all-in-one mail server.
|
||||||
|
|
||||||
|
Maddy Mail Server implements all functionality required to run a e-mail
|
||||||
|
server. It can send messages via SMTP (works as MTA), accept messages via SMTP
|
||||||
|
(works as MX) and store messages while providing access to them via IMAP.
|
||||||
|
In addition to that it implements auxiliary protocols that are mandatory
|
||||||
|
to keep email reasonably secure (DKIM, SPF, DMARC, DANE, MTA-STS).
|
||||||
|
|
||||||
|
It replaces Postfix, Dovecot, OpenDKIM, OpenSPF, OpenDMARC and more with one
|
||||||
|
daemon with uniform configuration and minimal maintenance cost.
|
||||||
|
|
||||||
|
**Note:** IMAP storage is "beta". If you are looking for stable and
|
||||||
|
feature-packed implementation you may want to use Dovecot instead. maddy still
|
||||||
|
can handle message delivery business.
|
||||||
|
|
||||||
|
[](https://github.com/foxcpp/maddy/actions/workflows/cicd.yml)
|
||||||
|
[](https://github.com/foxcpp/maddy)
|
||||||
|
|
||||||
|
* [Setup tutorial](https://maddy.email/tutorials/setting-up/)
|
||||||
|
* [Documentation](https://maddy.email/)
|
||||||
|
|
||||||
|
* [IRC channel](https://webchat.oftc.net/?channels=maddy&uio=MT11bmRlZmluZWQb1)
|
||||||
|
* [Mailing list](https://lists.sr.ht/~foxcpp/maddy)
|
201
build.sh
Executable file
201
build.sh
Executable file
@ -0,0 +1,201 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
destdir=/
|
||||||
|
builddir="$PWD/build"
|
||||||
|
prefix=/usr/local
|
||||||
|
version=
|
||||||
|
static=0
|
||||||
|
if [ "${GOFLAGS}" = "" ]; then
|
||||||
|
GOFLAGS="-trimpath" # set some flags to avoid passing "" to go
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_help() {
|
||||||
|
cat >&2 <<EOF
|
||||||
|
Usage:
|
||||||
|
./build.sh [options] {build,install}
|
||||||
|
|
||||||
|
Script to build, package or install Maddy Mail Server.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help guess!
|
||||||
|
--builddir directory to build in (default: $builddir)
|
||||||
|
|
||||||
|
Options for ./build.sh build:
|
||||||
|
--static build static self-contained executables (musl-libc recommended)
|
||||||
|
--tags <tags> build tags to use
|
||||||
|
--version <version> version tag to embed into executables (default: auto-detect)
|
||||||
|
|
||||||
|
Additional flags for "go build" can be provided using GOFLAGS environment variable.
|
||||||
|
|
||||||
|
Options for ./build.sh install:
|
||||||
|
--prefix <path> installation prefix (default: $prefix)
|
||||||
|
--destdir <path> system root (default: $destdir)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
print_help
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
--builddir)
|
||||||
|
shift
|
||||||
|
builddir="$1"
|
||||||
|
;;
|
||||||
|
--prefix)
|
||||||
|
shift
|
||||||
|
prefix="$1"
|
||||||
|
;;
|
||||||
|
--destdir)
|
||||||
|
shift
|
||||||
|
destdir="$1"
|
||||||
|
;;
|
||||||
|
--version)
|
||||||
|
shift
|
||||||
|
version="$1"
|
||||||
|
;;
|
||||||
|
--static)
|
||||||
|
static=1
|
||||||
|
;;
|
||||||
|
--tags)
|
||||||
|
shift
|
||||||
|
tags="$1"
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
break
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-?*)
|
||||||
|
echo "Unknown option: ${1}. See --help." >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
configdir="${destdir}etc/maddy"
|
||||||
|
|
||||||
|
if [ "$version" = "" ]; then
|
||||||
|
version=unknown
|
||||||
|
if [ -e .version ]; then
|
||||||
|
version="$(cat .version)"
|
||||||
|
fi
|
||||||
|
if [ -e .git ] && command -v git 2>/dev/null >/dev/null; then
|
||||||
|
version="${version}+$(git rev-parse --short HEAD)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
build_man_pages() {
|
||||||
|
set +e
|
||||||
|
if ! command -v scdoc >/dev/null 2>/dev/null; then
|
||||||
|
echo '-- [!] No scdoc utility found. Skipping man pages building.' >&2
|
||||||
|
set -e
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo '-- Building man pages...' >&2
|
||||||
|
|
||||||
|
mkdir -p "${builddir}/man"
|
||||||
|
for f in ./docs/man/*.1.scd; do
|
||||||
|
scdoc < "$f" > "${builddir}/man/$(basename "$f" .scd)"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
mkdir -p "${builddir}"
|
||||||
|
echo "-- Version: ${version}" >&2
|
||||||
|
if [ "$(go env CC)" = "" ]; then
|
||||||
|
echo '-- [!] No C compiler available. maddy will be built without SQLite3 support and default configuration will be unusable.' >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$static" -eq 1 ]; then
|
||||||
|
echo "-- Building main server executable..." >&2
|
||||||
|
# This is literally impossible to specify this line of arguments as part of ${GOFLAGS}
|
||||||
|
# using only POSIX sh features (and even with Bash extensions I can't figure it out).
|
||||||
|
go build -trimpath -buildmode pie -tags "$tags osusergo netgo static_build" \
|
||||||
|
-ldflags "-extldflags '-fno-PIC -static' -X \"github.com/foxcpp/maddy.Version=${version}\"" \
|
||||||
|
-o "${builddir}/maddy" ${GOFLAGS} ./cmd/maddy
|
||||||
|
else
|
||||||
|
echo "-- Building main server executable..." >&2
|
||||||
|
go build -tags "$tags" -trimpath -ldflags="-X \"github.com/foxcpp/maddy.Version=${version}\"" -o "${builddir}/maddy" ${GOFLAGS} ./cmd/maddy
|
||||||
|
fi
|
||||||
|
|
||||||
|
build_man_pages
|
||||||
|
|
||||||
|
echo "-- Copying misc files..." >&2
|
||||||
|
|
||||||
|
mkdir -p "${builddir}/systemd"
|
||||||
|
cp dist/systemd/*.service "${builddir}/systemd/"
|
||||||
|
cp maddy.conf "${builddir}/maddy.conf"
|
||||||
|
}
|
||||||
|
|
||||||
|
install() {
|
||||||
|
echo "-- Installing built files..." >&2
|
||||||
|
|
||||||
|
command install -m 0755 -d "${destdir}/${prefix}/bin/"
|
||||||
|
command install -m 0755 "${builddir}/maddy" "${destdir}/${prefix}/bin/"
|
||||||
|
command ln -sf maddy "${destdir}/${prefix}/bin/maddyctl"
|
||||||
|
command install -m 0755 -d "${configdir}"
|
||||||
|
|
||||||
|
|
||||||
|
# We do not want to overwrite existing configuration.
|
||||||
|
# If the file exists, then save it with .default suffix and warn user.
|
||||||
|
if [ ! -e "${configdir}/maddy.conf" ]; then
|
||||||
|
command install -m 0644 ./maddy.conf "${configdir}/maddy.conf"
|
||||||
|
else
|
||||||
|
echo "-- [!] Configuration file ${configdir}/maddy.conf exists, saving to ${configdir}/maddy.conf.default" >&2
|
||||||
|
command install -m 0644 ./maddy.conf "${configdir}/maddy.conf.default"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Attempt to install systemd units only for Linux.
|
||||||
|
# Check is done using GOOS instead of uname -s to account for possible
|
||||||
|
# package cross-compilation.
|
||||||
|
# Though go command might be unavailable if build.sh is run
|
||||||
|
# with sudo and go installation is user-specific, so fallback
|
||||||
|
# to using uname -s in the end.
|
||||||
|
set +e
|
||||||
|
if command -v go >/dev/null 2>/dev/null; then
|
||||||
|
set -e
|
||||||
|
if [ "$(go env GOOS)" = "linux" ]; then
|
||||||
|
command install -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/"
|
||||||
|
command install -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
set -e
|
||||||
|
if [ "$(uname -s)" = "Linux" ]; then
|
||||||
|
command install -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/"
|
||||||
|
command install -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "${builddir}"/man ]; then
|
||||||
|
command install -m 0755 -d "${destdir}/${prefix}/share/man/man1/"
|
||||||
|
for f in "${builddir}"/man/*.1; do
|
||||||
|
command install -m 0644 "$f" "${destdir}/${prefix}/share/man/man1/"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Old build.sh compatibility
|
||||||
|
install_pkg() {
|
||||||
|
echo "-- [!] Replace 'install_pkg' with 'install' in build.sh invocation" >&2
|
||||||
|
install
|
||||||
|
}
|
||||||
|
package() {
|
||||||
|
echo "-- [!] Replace 'package' with 'build' in build.sh invocation" >&2
|
||||||
|
build
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
build
|
||||||
|
else
|
||||||
|
for arg in "$@"; do
|
||||||
|
eval "$arg"
|
||||||
|
done
|
||||||
|
fi
|
11
cmd/README.md
Normal file
11
cmd/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
maddy executables
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### maddy
|
||||||
|
|
||||||
|
Main server executable.
|
||||||
|
|
||||||
|
### maddy-pam-helper, maddy-shadow-helper
|
||||||
|
|
||||||
|
Utilities compatible with the auth.external module that call libpam or read
|
||||||
|
/etc/shadow on Unix systems.
|
65
cmd/maddy-pam-helper/README.md
Normal file
65
cmd/maddy-pam-helper/README.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
## maddy-pam-helper
|
||||||
|
|
||||||
|
External setuid binary for interaction with shadow passwords database or other
|
||||||
|
privileged objects necessary to run PAM authentication.
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
It is really easy to build it using any GCC:
|
||||||
|
```
|
||||||
|
gcc pam.c main.c -lpam -o maddy-pam-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
Yes, it is not a Go binary.
|
||||||
|
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
maddy-pam-helper is kinda dangerous binary and should not be allowed to be
|
||||||
|
executed by everybody but maddy's user. At the same moment it needs to have
|
||||||
|
access to read-protected files. For this reason installation should be done
|
||||||
|
very carefully to make sure to not introduce any security "holes".
|
||||||
|
|
||||||
|
#### First method
|
||||||
|
|
||||||
|
```shell
|
||||||
|
chown maddy: /usr/bin/maddy-pam-helper
|
||||||
|
chmod u+x,g-x,o-x /usr/bin/maddy-pam-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
Also maddy-pam-helper needs access to /etc/shadow, one of the ways to provide
|
||||||
|
it is to set file capability CAP_DAC_READ_SEARCH:
|
||||||
|
```
|
||||||
|
setcap cap_dac_read_search+ep /usr/bin/maddy-pam-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Second method
|
||||||
|
|
||||||
|
Another, less restrictive is to make it setuid-root (assuming you have both maddy user and group):
|
||||||
|
```
|
||||||
|
chown root:maddy /usr/bin/maddy-pam-helper
|
||||||
|
chmod u+xs,g+x,o-x /usr/bin/maddy-pam-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Third method
|
||||||
|
|
||||||
|
The best way actually is to create `shadow` group and grant access to
|
||||||
|
/etc/shadow to it and then make maddy-pam-helper setgid-shadow:
|
||||||
|
```
|
||||||
|
groupadd shadow
|
||||||
|
chown :shadow /etc/shadow
|
||||||
|
chmod g+r /etc/shadow
|
||||||
|
chown maddy:shadow /usr/bin/maddy-pam-helper
|
||||||
|
chmod u+x,g+xs /usr/bin/maddy-pam-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
Pick what works best for you.
|
||||||
|
|
||||||
|
### PAM service
|
||||||
|
|
||||||
|
maddy-pam-helper uses custom service instead of pretending to be su or sudo.
|
||||||
|
Because of this you should configure PAM to accept it.
|
||||||
|
|
||||||
|
Minimal example using local passwd/shadow database for authentication can be
|
||||||
|
found in [maddy.conf][maddy.conf] file.
|
||||||
|
It should be put into /etc/pam.d/maddy.
|
3
cmd/maddy-pam-helper/maddy.conf
Normal file
3
cmd/maddy-pam-helper/maddy.conf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#%PAM-1.0
|
||||||
|
auth required pam_unix.so
|
||||||
|
account required pam_unix.so
|
51
cmd/maddy-pam-helper/main.c
Normal file
51
cmd/maddy-pam-helper/main.c
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include "pam.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
I really doubt it is a good idea to bring Go to the binary whose primary task
|
||||||
|
is to call libpam using CGo anyway.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int run(void) {
|
||||||
|
char *username = NULL, *password = NULL;
|
||||||
|
size_t username_buf_len = 0, password_buf_len = 0;
|
||||||
|
|
||||||
|
ssize_t username_len = getline(&username, &username_buf_len, stdin);
|
||||||
|
if (username_len < 0) {
|
||||||
|
perror("getline username");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t password_len = getline(&password, &password_buf_len, stdin);
|
||||||
|
if (password_len < 0) {
|
||||||
|
perror("getline password");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut trailing \n.
|
||||||
|
if (username_len > 0) {
|
||||||
|
username[username_len - 1] = 0;
|
||||||
|
}
|
||||||
|
if (password_len > 0) {
|
||||||
|
password[password_len - 1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct error_obj err = run_pam_auth(username, password);
|
||||||
|
if (err.status != 0) {
|
||||||
|
if (err.status == 2) {
|
||||||
|
fprintf(stderr, "%s: %s\n", err.func_name, err.error_msg);
|
||||||
|
}
|
||||||
|
return err.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef CGO
|
||||||
|
int main() {
|
||||||
|
return run();
|
||||||
|
}
|
||||||
|
#endif
|
38
cmd/maddy-pam-helper/main.go
Normal file
38
cmd/maddy-pam-helper/main.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Maddy Mail Server - Composable all-in-one email server.
|
||||||
|
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lpam
|
||||||
|
#cgo CFLAGS: -DCGO -Wall -Wextra -Werror -Wno-unused-parameter -Wno-error=unused-parameter -Wpedantic -std=c99
|
||||||
|
extern int run();
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Apparently, some people would not want to build it manually by calling GCC.
|
||||||
|
Here we do it for them. Not going to tell them that resulting file is 800KiB
|
||||||
|
bigger than one built using only C compiler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
i := int(C.run())
|
||||||
|
os.Exit(i)
|
||||||
|
}
|
100
cmd/maddy-pam-helper/pam.c
Normal file
100
cmd/maddy-pam-helper/pam.c
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
Maddy Mail Server - Composable all-in-one email server.
|
||||||
|
Copyright © 2019-2022 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include "pam.h"
|
||||||
|
|
||||||
|
static int conv_func(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) {
|
||||||
|
struct pam_response *reply = malloc(sizeof(struct pam_response));
|
||||||
|
if (reply == NULL) {
|
||||||
|
return PAM_CONV_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* password_cpy = malloc(strlen((char*)appdata_ptr)+1);
|
||||||
|
if (password_cpy == NULL) {
|
||||||
|
return PAM_CONV_ERR;
|
||||||
|
}
|
||||||
|
memcpy(password_cpy, (char*)appdata_ptr, strlen((char*)appdata_ptr)+1);
|
||||||
|
|
||||||
|
reply->resp = password_cpy;
|
||||||
|
reply->resp_retcode = 0;
|
||||||
|
|
||||||
|
// PAM frees pam_response for us.
|
||||||
|
*resp = reply;
|
||||||
|
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct error_obj run_pam_auth(const char *username, char *password) {
|
||||||
|
const struct pam_conv local_conv = { conv_func, password };
|
||||||
|
pam_handle_t *local_auth = NULL;
|
||||||
|
int status = pam_start("maddy", username, &local_conv, &local_auth);
|
||||||
|
if (status != PAM_SUCCESS) {
|
||||||
|
struct error_obj ret_val;
|
||||||
|
ret_val.status = 2;
|
||||||
|
ret_val.func_name = "pam_start";
|
||||||
|
ret_val.error_msg = pam_strerror(local_auth, status);
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = pam_authenticate(local_auth, PAM_SILENT|PAM_DISALLOW_NULL_AUTHTOK);
|
||||||
|
if (status != PAM_SUCCESS) {
|
||||||
|
struct error_obj ret_val;
|
||||||
|
if (status == PAM_AUTH_ERR || status == PAM_USER_UNKNOWN) {
|
||||||
|
ret_val.status = 1;
|
||||||
|
} else {
|
||||||
|
ret_val.status = 2;
|
||||||
|
}
|
||||||
|
ret_val.func_name = "pam_authenticate";
|
||||||
|
ret_val.error_msg = pam_strerror(local_auth, status);
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = pam_acct_mgmt(local_auth, PAM_SILENT|PAM_DISALLOW_NULL_AUTHTOK);
|
||||||
|
if (status != PAM_SUCCESS) {
|
||||||
|
struct error_obj ret_val;
|
||||||
|
if (status == PAM_AUTH_ERR || status == PAM_USER_UNKNOWN || status == PAM_NEW_AUTHTOK_REQD) {
|
||||||
|
ret_val.status = 1;
|
||||||
|
} else {
|
||||||
|
ret_val.status = 2;
|
||||||
|
}
|
||||||
|
ret_val.func_name = "pam_acct_mgmt";
|
||||||
|
ret_val.error_msg = pam_strerror(local_auth, status);
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = pam_end(local_auth, status);
|
||||||
|
if (status != PAM_SUCCESS) {
|
||||||
|
struct error_obj ret_val;
|
||||||
|
ret_val.status = 2;
|
||||||
|
ret_val.func_name = "pam_end";
|
||||||
|
ret_val.error_msg = pam_strerror(local_auth, status);
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct error_obj ret_val;
|
||||||
|
ret_val.status = 0;
|
||||||
|
ret_val.func_name = NULL;
|
||||||
|
ret_val.error_msg = NULL;
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
27
cmd/maddy-pam-helper/pam.h
Normal file
27
cmd/maddy-pam-helper/pam.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Maddy Mail Server - Composable all-in-one email server.
|
||||||
|
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct error_obj {
|
||||||
|
int status;
|
||||||
|
const char* func_name;
|
||||||
|
const char* error_msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct error_obj run_pam_auth(const char *username, char *password);
|
47
cmd/maddy-shadow-helper/README.md
Normal file
47
cmd/maddy-shadow-helper/README.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
## maddy-shadow-helper
|
||||||
|
|
||||||
|
External helper binary for interaction with shadow passwords database.
|
||||||
|
Unlike maddy-pam-helper it supports only local shadow database but it does
|
||||||
|
not have any C dependencies.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
maddy-shadow-helper is kinda dangerous binary and should not be allowed to be
|
||||||
|
executed by everybody but maddy's user. At the same moment it needs to have
|
||||||
|
access to read-protected files. For this reason installation should be done
|
||||||
|
very carefully to make sure to not introduce any security "holes".
|
||||||
|
|
||||||
|
#### First method
|
||||||
|
|
||||||
|
```shell
|
||||||
|
chown maddy: /usr/bin/maddy-shadow-helper
|
||||||
|
chmod u+x,g-x,o-x /usr/bin/maddy-shadow-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
Also maddy-shadow-helper needs access to /etc/shadow, one of the ways to provide
|
||||||
|
it is to set file capability CAP_DAC_READ_SEARCH:
|
||||||
|
```
|
||||||
|
setcap cap_dac_read_search+ep /usr/bin/maddy-shadow-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Second method
|
||||||
|
|
||||||
|
Another, less restrictive is to make it setuid-root (assuming you have both maddy user and group):
|
||||||
|
```
|
||||||
|
chown root:maddy /usr/bin/maddy-shadow-helper
|
||||||
|
chmod u+xs,g+x,o-x /usr/bin/maddy-shadow-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Third method
|
||||||
|
|
||||||
|
The best way actually is to create `shadow` group and grant access to
|
||||||
|
/etc/shadow to it and then make maddy-shadow-helper setgid-shadow:
|
||||||
|
```
|
||||||
|
groupadd shadow
|
||||||
|
chown :shadow /etc/shadow
|
||||||
|
chmod g+r /etc/shadow
|
||||||
|
chown maddy:shadow /usr/bin/maddy-shadow-helper
|
||||||
|
chmod u+x,g+xs /usr/bin/maddy-shadow-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
Pick what works best for you.
|
71
cmd/maddy-shadow-helper/main.go
Normal file
71
cmd/maddy-shadow-helper/main.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
Maddy Mail Server - Composable all-in-one email server.
|
||||||
|
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/foxcpp/maddy/internal/auth/shadow"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
scnr := bufio.NewScanner(os.Stdin)
|
||||||
|
|
||||||
|
if !scnr.Scan() {
|
||||||
|
fmt.Fprintln(os.Stderr, scnr.Err())
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
username := scnr.Text()
|
||||||
|
|
||||||
|
if !scnr.Scan() {
|
||||||
|
fmt.Fprintln(os.Stderr, scnr.Err())
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
password := scnr.Text()
|
||||||
|
|
||||||
|
ent, err := shadow.Lookup(username)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shadow.ErrNoSuchUser) {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ent.IsAccountValid() {
|
||||||
|
fmt.Fprintln(os.Stderr, "account is expired")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ent.IsPasswordValid() {
|
||||||
|
fmt.Fprintln(os.Stderr, "password is expired")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ent.VerifyPassword(password); err != nil {
|
||||||
|
if errors.Is(err, shadow.ErrWrongPassword) {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
29
cmd/maddy/main.go
Normal file
29
cmd/maddy/main.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Maddy Mail Server - Composable all-in-one email server.
|
||||||
|
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/foxcpp/maddy"
|
||||||
|
maddycli "github.com/foxcpp/maddy/internal/cli"
|
||||||
|
_ "github.com/foxcpp/maddy/internal/cli/ctl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
maddycli.Run()
|
||||||
|
}
|
120
config.go
Normal file
120
config.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
Maddy Mail Server - Composable all-in-one email server.
|
||||||
|
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package maddy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/foxcpp/maddy/framework/config"
|
||||||
|
"github.com/foxcpp/maddy/framework/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Config matchers for module interfaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// logOut structure wraps log.Output and preserves
|
||||||
|
// configuration directive it was constructed from, allowing
|
||||||
|
// dynamic reinitialization for purposes of log file rotation.
|
||||||
|
type logOut struct {
|
||||||
|
args []string
|
||||||
|
log.Output
|
||||||
|
}
|
||||||
|
|
||||||
|
func logOutput(_ *config.Map, node config.Node) (interface{}, error) {
|
||||||
|
if len(node.Args) == 0 {
|
||||||
|
return nil, config.NodeErr(node, "expected at least 1 argument")
|
||||||
|
}
|
||||||
|
if len(node.Children) != 0 {
|
||||||
|
return nil, config.NodeErr(node, "can't declare block here")
|
||||||
|
}
|
||||||
|
|
||||||
|
return LogOutputOption(node.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogOutputOption(args []string) (log.Output, error) {
|
||||||
|
outs := make([]log.Output, 0, len(args))
|
||||||
|
for i, arg := range args {
|
||||||
|
switch arg {
|
||||||
|
case "stderr":
|
||||||
|
outs = append(outs, log.WriterOutput(os.Stderr, false))
|
||||||
|
case "stderr_ts":
|
||||||
|
outs = append(outs, log.WriterOutput(os.Stderr, true))
|
||||||
|
case "syslog":
|
||||||
|
syslogOut, err := log.SyslogOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to syslog daemon: %v", err)
|
||||||
|
}
|
||||||
|
outs = append(outs, syslogOut)
|
||||||
|
case "off":
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, errors.New("'off' can't be combined with other log targets")
|
||||||
|
}
|
||||||
|
return log.NopOutput{}, nil
|
||||||
|
default:
|
||||||
|
// Log file paths are converted to absolute to make sure
|
||||||
|
// we will be able to recreate them in right location
|
||||||
|
// after changing working directory to the state dir.
|
||||||
|
absPath, err := filepath.Abs(arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We change the actual argument, so logOut object will
|
||||||
|
// keep the absolute path for reinitialization.
|
||||||
|
args[i] = absPath
|
||||||
|
|
||||||
|
w, err := os.OpenFile(absPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create log file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outs = append(outs, log.WriteCloserOutput(w, true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outs) == 1 {
|
||||||
|
return logOut{args, outs[0]}, nil
|
||||||
|
}
|
||||||
|
return logOut{args, log.MultiOutput(outs...)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultLogOutput() (interface{}, error) {
|
||||||
|
return log.DefaultLogger.Out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reinitLogging() {
|
||||||
|
out, ok := log.DefaultLogger.Out.(logOut)
|
||||||
|
if !ok {
|
||||||
|
log.Println("Can't reinitialize logger because it was replaced before, this is a bug")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newOut, err := LogOutputOption(out.args)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Can't reinitialize logger:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Close()
|
||||||
|
|
||||||
|
log.DefaultLogger.Out = newOut
|
||||||
|
}
|
6
contrib/README.md
Normal file
6
contrib/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Community contributed resources
|
||||||
|
|
||||||
|
Disclaimer: Nothing inside subdirectories here is directly supported by Maddy
|
||||||
|
Mail Server maintainers. Some community members may be able to help you or not.
|
||||||
|
|
||||||
|
- Kubernetes helm chart is maintained by @acim.
|
23
contrib/kubernetes/chart/.helmignore
Normal file
23
contrib/kubernetes/chart/.helmignore
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
23
contrib/kubernetes/chart/Chart.yaml
Normal file
23
contrib/kubernetes/chart/Chart.yaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: maddy
|
||||||
|
description: A Helm chart for Kubernetes
|
||||||
|
|
||||||
|
# A chart can be either an 'application' or a 'library' chart.
|
||||||
|
#
|
||||||
|
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||||
|
# to be deployed.
|
||||||
|
#
|
||||||
|
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||||
|
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||||
|
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||||
|
type: application
|
||||||
|
|
||||||
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
|
# to the chart and its templates, including the app version.
|
||||||
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
|
version: 0.2.6
|
||||||
|
|
||||||
|
# This is the version number of the application being deployed. This version number should be
|
||||||
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
|
appVersion: 0.4.0
|
69
contrib/kubernetes/chart/README.md
Normal file
69
contrib/kubernetes/chart/README.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# maddy Helm chart for Kubernetes
|
||||||
|
|
||||||
|
  
|
||||||
|
|
||||||
|
This is just initial effort to run maddy within Kubernetes cluster. We have used Deployment resource which has some downsides
|
||||||
|
but at least this chart will allow you to install maddy relatively easily on your Kubernetes cluster. We have considered
|
||||||
|
StatefulSet and DaemonSet but such solutions would require much more configuration and in casae of DaemonSet also a TCP
|
||||||
|
load balancer in front of the nodes.
|
||||||
|
|
||||||
|
## Requirement
|
||||||
|
|
||||||
|
In order to run maddy properly, you need to have TLS secret under name maddy present in the cluster. If you have commercial
|
||||||
|
certificate, you can create it by the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kubectl create secret tls maddy --cert=fullchain.pem --key=privkey.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use cert-manager, just create the secret under name maddy.
|
||||||
|
|
||||||
|
## Replication
|
||||||
|
|
||||||
|
Default for this chart is 1 replica of maddy. If you try to increase this, you will probably get an error because of
|
||||||
|
the busy ports 25, 143, 587, etc. We do not support this feature at the moment, so please use just 1 replica. Like said
|
||||||
|
at the beginning of this document, multiple replicas would probably require to switch do DaemonSet which would further require
|
||||||
|
to have TCP load balancer and shared storage between all replicas. This is not supported by this chart, sorry.
|
||||||
|
This chart is used on one node cluster and then installation is straight forward, like described bellow, but if you have
|
||||||
|
multiple node cluster, please use taints and tolerations to select the desired node. This chart supports tolerations to
|
||||||
|
be set.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Key | Type | Default | Description |
|
||||||
|
| -------------------------- | ------ | ----------------- | ----------- |
|
||||||
|
| affinity | object | `{}` | |
|
||||||
|
| fullnameOverride | string | `""` | |
|
||||||
|
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||||
|
| image.repository | string | `"foxcpp/maddy"` | |
|
||||||
|
| image.tag | string | `""` | |
|
||||||
|
| imagePullSecrets | list | `[]` | |
|
||||||
|
| nameOverride | string | `""` | |
|
||||||
|
| nodeSelector | object | `{}` | |
|
||||||
|
| persistence.accessMode | string | `"ReadWriteOnce"` | |
|
||||||
|
| persistence.annotations | object | `{}` | |
|
||||||
|
| persistence.enabled | bool | `false` | |
|
||||||
|
| persistence.path | string | `"/data"` | |
|
||||||
|
| persistence.size | string | `"128Mi"` | |
|
||||||
|
| podAnnotations | object | `{}` | |
|
||||||
|
| podSecurityContext | object | `{}` | |
|
||||||
|
| replicaCount | int | `1` | |
|
||||||
|
| resources | object | `{}` | |
|
||||||
|
| securityContext | object | `{}` | |
|
||||||
|
| service.type | string | `"NodePort"` | |
|
||||||
|
| serviceAccount.annotations | object | `{}` | |
|
||||||
|
| serviceAccount.create | bool | `true` | |
|
||||||
|
| serviceAccount.name | string | `""` | |
|
||||||
|
| tolerations | list | `[]` | |
|
||||||
|
|
||||||
|
## Installing the chart
|
||||||
|
|
||||||
|
```sh
|
||||||
|
helm upgrade --install maddy ./chart --set service.externapIPs[0]=1.2.3.4
|
||||||
|
```
|
||||||
|
|
||||||
|
1.2.3.4 is your public IP of the node.
|
||||||
|
|
||||||
|
## maddy configuration
|
||||||
|
|
||||||
|
Feel free to tweak files/maddy.conf and files/aliases according to your needs.
|
1
contrib/kubernetes/chart/files/aliases
Normal file
1
contrib/kubernetes/chart/files/aliases
Normal file
@ -0,0 +1 @@
|
|||||||
|
info@example.org: foxcpp@example.org
|
171
contrib/kubernetes/chart/files/maddy.conf
Normal file
171
contrib/kubernetes/chart/files/maddy.conf
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
## maddy 0.3 - default configuration file (2020-05-31)
|
||||||
|
# Suitable for small-scale deployments. Uses its own format for local users DB,
|
||||||
|
# should be managed via maddy subcommands.
|
||||||
|
#
|
||||||
|
# See tutorials at https://foxcpp.dev/maddy for guidance on typical
|
||||||
|
# configuration changes.
|
||||||
|
#
|
||||||
|
# See manual pages (also available at https://foxcpp.dev/maddy) for reference
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Base variables
|
||||||
|
|
||||||
|
$(hostname) = mx1.example.org
|
||||||
|
$(primary_domain) = example.org
|
||||||
|
$(local_domains) = $(primary_domain)
|
||||||
|
|
||||||
|
tls file /etc/maddy/certs/fullchain.pem /etc/maddy/certs/privkey.pem
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Local storage & authentication
|
||||||
|
|
||||||
|
# pass_table provides local hashed passwords storage for authentication of
|
||||||
|
# users. It can be configured to use any "table" module, in default
|
||||||
|
# configuration a table in SQLite DB is used.
|
||||||
|
# Table can be replaced to use e.g. a file for passwords. Or pass_table module
|
||||||
|
# can be replaced altogether to use some external source of credentials (e.g.
|
||||||
|
# PAM, /etc/shadow file).
|
||||||
|
#
|
||||||
|
# If table module supports it (sql_table does) - credentials can be managed
|
||||||
|
# using 'maddy creds' command.
|
||||||
|
|
||||||
|
auth.pass_table local_authdb {
|
||||||
|
table sql_table {
|
||||||
|
driver sqlite3
|
||||||
|
dsn credentials.db
|
||||||
|
table_name passwords
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# imapsql module stores all indexes and metadata necessary for IMAP using a
|
||||||
|
# relational database. It is used by IMAP endpoint for mailbox access and
|
||||||
|
# also by SMTP & Submission endpoints for delivery of local messages.
|
||||||
|
#
|
||||||
|
# IMAP accounts, mailboxes and all message metadata can be inspected using
|
||||||
|
# imap-* subcommands of maddy.
|
||||||
|
|
||||||
|
storage.imapsql local_mailboxes {
|
||||||
|
driver sqlite3
|
||||||
|
dsn imapsql.db
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# SMTP endpoints + message routing
|
||||||
|
|
||||||
|
hostname $(hostname)
|
||||||
|
|
||||||
|
msgpipeline local_routing {
|
||||||
|
dmarc yes
|
||||||
|
check {
|
||||||
|
require_matching_ehlo
|
||||||
|
require_mx_record
|
||||||
|
dkim
|
||||||
|
spf
|
||||||
|
}
|
||||||
|
|
||||||
|
# Insert handling for special-purpose local domains here.
|
||||||
|
# e.g.
|
||||||
|
# destination lists.example.org {
|
||||||
|
# deliver_to lmtp tcp://127.0.0.1:8024
|
||||||
|
# }
|
||||||
|
|
||||||
|
destination postmaster $(local_domains) {
|
||||||
|
modify {
|
||||||
|
replace_rcpt regexp "(.+)\+(.+)@(.+)" "$1@$3"
|
||||||
|
replace_rcpt file /data/aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
deliver_to &local_mailboxes
|
||||||
|
}
|
||||||
|
|
||||||
|
default_destination {
|
||||||
|
reject 550 5.1.1 "User doesn't exist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
smtp tcp://0.0.0.0:25 {
|
||||||
|
limits {
|
||||||
|
# Up to 20 msgs/sec across max. 10 SMTP connections.
|
||||||
|
all rate 20 1s
|
||||||
|
all concurrency 10
|
||||||
|
}
|
||||||
|
|
||||||
|
source $(local_domains) {
|
||||||
|
reject 501 5.1.8 "Use Submission for outgoing SMTP"
|
||||||
|
}
|
||||||
|
default_source {
|
||||||
|
destination postmaster $(local_domains) {
|
||||||
|
deliver_to &local_routing
|
||||||
|
}
|
||||||
|
default_destination {
|
||||||
|
reject 550 5.1.1 "User doesn't exist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
|
||||||
|
limits {
|
||||||
|
# Up to 50 msgs/sec across any amount of SMTP connections.
|
||||||
|
all rate 50 1s
|
||||||
|
}
|
||||||
|
|
||||||
|
auth &local_authdb
|
||||||
|
|
||||||
|
source $(local_domains) {
|
||||||
|
destination postmaster $(local_domains) {
|
||||||
|
deliver_to &local_routing
|
||||||
|
}
|
||||||
|
default_destination {
|
||||||
|
modify {
|
||||||
|
dkim $(primary_domain) $(local_domains) default
|
||||||
|
}
|
||||||
|
deliver_to &remote_queue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default_source {
|
||||||
|
reject 501 5.1.8 "Non-local sender domain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target.remote outbound_delivery {
|
||||||
|
limits {
|
||||||
|
# Up to 20 msgs/sec across max. 10 SMTP connections
|
||||||
|
# for each recipient domain.
|
||||||
|
destination rate 20 1s
|
||||||
|
destination concurrency 10
|
||||||
|
}
|
||||||
|
mx_auth {
|
||||||
|
dane
|
||||||
|
mtasts {
|
||||||
|
cache fs
|
||||||
|
fs_dir mtasts_cache/
|
||||||
|
}
|
||||||
|
local_policy {
|
||||||
|
min_tls_level encrypted
|
||||||
|
min_mx_level none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target.queue remote_queue {
|
||||||
|
target &outbound_delivery
|
||||||
|
|
||||||
|
autogenerated_msg_domain $(primary_domain)
|
||||||
|
bounce {
|
||||||
|
destination postmaster $(local_domains) {
|
||||||
|
deliver_to &local_routing
|
||||||
|
}
|
||||||
|
default_destination {
|
||||||
|
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# IMAP endpoints
|
||||||
|
|
||||||
|
imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
|
||||||
|
auth &local_authdb
|
||||||
|
storage &local_mailboxes
|
||||||
|
}
|
0
contrib/kubernetes/chart/templates/NOTES.txt
Normal file
0
contrib/kubernetes/chart/templates/NOTES.txt
Normal file
62
contrib/kubernetes/chart/templates/_helpers.tpl
Normal file
62
contrib/kubernetes/chart/templates/_helpers.tpl
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "maddy.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "maddy.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "maddy.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "maddy.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "maddy.chart" . }}
|
||||||
|
{{ include "maddy.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "maddy.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "maddy.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "maddy.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "maddy.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
10
contrib/kubernetes/chart/templates/configmap.yaml
Normal file
10
contrib/kubernetes/chart/templates/configmap.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{include "maddy.fullname" .}}
|
||||||
|
labels: {{- include "maddy.labels" . | nindent 4}}
|
||||||
|
data:
|
||||||
|
maddy.conf: |
|
||||||
|
{{ tpl (.Files.Get "files/maddy.conf") . | printf "%s" | indent 4 }}
|
||||||
|
aliases: |
|
||||||
|
{{ tpl (.Files.Get "files/aliases") . | printf "%s" | indent 4 }}
|
113
contrib/kubernetes/chart/templates/deployment.yaml
Normal file
113
contrib/kubernetes/chart/templates/deployment.yaml
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "maddy.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "maddy.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "maddy.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
checksum/config: {{ tpl (.Files.Get "files/maddy.conf") . | printf "%s" | sha256sum }}
|
||||||
|
checksum/aliases: {{ tpl (.Files.Get "files/aliases") . | printf "%s" | sha256sum }}
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "maddy.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ include "maddy.serviceAccountName" . }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
initContainers:
|
||||||
|
- name: init
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
image: busybox
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- cp /tmp/maddy/* /data/.
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: {{ .Values.persistence.path }}
|
||||||
|
{{- if .Values.persistence.subPath }}
|
||||||
|
subPath: {{ .Values.persistence.subPath }}
|
||||||
|
{{- end }}
|
||||||
|
- name: config
|
||||||
|
mountPath: /tmp/maddy
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: smtp
|
||||||
|
containerPort: 25
|
||||||
|
protocol: TCP
|
||||||
|
- name: imaps
|
||||||
|
containerPort: 993
|
||||||
|
protocol: TCP
|
||||||
|
- name: starttls
|
||||||
|
containerPort: 587
|
||||||
|
protocol: TCP
|
||||||
|
# livenessProbe:
|
||||||
|
# httpGet:
|
||||||
|
# path: /
|
||||||
|
# port: http
|
||||||
|
# readinessProbe:
|
||||||
|
# httpGet:
|
||||||
|
# path: /
|
||||||
|
# port: http
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: {{ .Values.persistence.path }}
|
||||||
|
{{- if .Values.persistence.subPath }}
|
||||||
|
subPath: {{ .Values.persistence.subPath }}
|
||||||
|
{{- end }}
|
||||||
|
- name: tls
|
||||||
|
mountPath: /etc/maddy/certs/fullchain.pem
|
||||||
|
subPath: tls.crt
|
||||||
|
- name: tls
|
||||||
|
mountPath: /etc/maddy/certs/privkey.pem
|
||||||
|
subPath: tls.key
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
{{- if .Values.persistence.enabled }}
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ default (include "maddy.fullname" .) .Values.persistence.existingClaim }}
|
||||||
|
{{- else }}
|
||||||
|
emptyDir: {}
|
||||||
|
{{- end }}
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: {{include "maddy.fullname" .}}
|
||||||
|
- name: tls
|
||||||
|
secret:
|
||||||
|
secretName: {{include "maddy.fullname" .}}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
22
contrib/kubernetes/chart/templates/pvc.yaml
Normal file
22
contrib/kubernetes/chart/templates/pvc.yaml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "maddy.fullname" . }}
|
||||||
|
annotations:
|
||||||
|
{{- with .Values.persistence.annotations }}
|
||||||
|
{{ toYaml . | indent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "maddy.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.accessMode }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.size }}
|
||||||
|
{{- if .Values.persistence.storageClass }}
|
||||||
|
storageClassName: {{ .Values.persistence.storageClass }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
|
|
27
contrib/kubernetes/chart/templates/service.yaml
Normal file
27
contrib/kubernetes/chart/templates/service.yaml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "maddy.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "maddy.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: 25
|
||||||
|
targetPort: smtp
|
||||||
|
protocol: TCP
|
||||||
|
name: smtp
|
||||||
|
- port: 993
|
||||||
|
targetPort: imaps
|
||||||
|
protocol: TCP
|
||||||
|
name: imaps
|
||||||
|
- port: 587
|
||||||
|
targetPort: starttls
|
||||||
|
protocol: TCP
|
||||||
|
name: starttls
|
||||||
|
selector:
|
||||||
|
{{- include "maddy.selectorLabels" . | nindent 4 }}
|
||||||
|
{{- with .Values.service.externalIPs }}
|
||||||
|
externalIPs:
|
||||||
|
{{- toYaml . | nindent 6 }}
|
||||||
|
{{- end -}}
|
12
contrib/kubernetes/chart/templates/serviceaccount.yaml
Normal file
12
contrib/kubernetes/chart/templates/serviceaccount.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{{- if .Values.serviceAccount.create -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "maddy.serviceAccountName" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "maddy.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.serviceAccount.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: "{{ include "maddy.fullname" . }}-test-connection"
|
||||||
|
labels:
|
||||||
|
{{- include "maddy.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": test-success
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: wget
|
||||||
|
image: busybox
|
||||||
|
command: ['wget']
|
||||||
|
args: ['{{ include "maddy.fullname" . }}:{{ .Values.service.port }}']
|
||||||
|
restartPolicy: Never
|
74
contrib/kubernetes/chart/values.yaml
Normal file
74
contrib/kubernetes/chart/values.yaml
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Default values for maddy.
|
||||||
|
# This is a YAML-formatted file.
|
||||||
|
# Declare variables to be passed into your templates.
|
||||||
|
|
||||||
|
replicaCount: 1 # Multiple replicas are not supported, please don't change this.
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: foxcpp/maddy
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
|
tag: ""
|
||||||
|
|
||||||
|
imagePullSecrets: []
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
serviceAccount:
|
||||||
|
# Specifies whether a service account should be created
|
||||||
|
create: true
|
||||||
|
# Annotations to add to the service account
|
||||||
|
annotations: {}
|
||||||
|
# The name of the service account to use.
|
||||||
|
# If not set and create is true, a name is generated using the fullname template
|
||||||
|
name: ""
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
podSecurityContext:
|
||||||
|
{}
|
||||||
|
# fsGroup: 2000
|
||||||
|
|
||||||
|
securityContext:
|
||||||
|
{}
|
||||||
|
# capabilities:
|
||||||
|
# drop:
|
||||||
|
# - ALL
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# runAsUser: 1000
|
||||||
|
|
||||||
|
# Set externalPIs to your public IP(s) of the node running maddy. In case of multiple nodes, you need to set tolerations
|
||||||
|
# and taints in order to run maddy on the exact node.
|
||||||
|
service:
|
||||||
|
type: NodePort
|
||||||
|
# externalIPs:
|
||||||
|
|
||||||
|
resources:
|
||||||
|
{}
|
||||||
|
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||||
|
# choice for the user. This also increases chances charts run on environments with little
|
||||||
|
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||||
|
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
# requests:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
enabled: false
|
||||||
|
# existingClaim: ""
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
size: 128Mi
|
||||||
|
# storageClass: ""
|
||||||
|
path: /data
|
||||||
|
annotations: {}
|
||||||
|
# subPath: "" # only mount a subpath of the Volume into the pod
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
46
directories.go
Normal file
46
directories.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//go:build !docker
|
||||||
|
// +build !docker
|
||||||
|
|
||||||
|
package maddy
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ConfigDirectory specifies platform-specific value
|
||||||
|
// that should be used as a location of default configuration
|
||||||
|
//
|
||||||
|
// It should not be changed and is defined as a variable
|
||||||
|
// only for purposes of modification using -X linker flag.
|
||||||
|
ConfigDirectory = "/etc/maddy"
|
||||||
|
|
||||||
|
// DefaultStateDirectory specifies platform-specific
|
||||||
|
// default for StateDirectory.
|
||||||
|
//
|
||||||
|
// Most code should use StateDirectory instead since
|
||||||
|
// it will contain the effective location of the state
|
||||||
|
// directory.
|
||||||
|
//
|
||||||
|
// It should not be changed and is defined as a variable
|
||||||
|
// only for purposes of modification using -X linker flag.
|
||||||
|
DefaultStateDirectory = "/var/lib/maddy"
|
||||||
|
|
||||||
|
// DefaultRuntimeDirectory specifies platform-specific
|
||||||
|
// default for RuntimeDirectory.
|
||||||
|
//
|
||||||
|
// Most code should use RuntimeDirectory instead since
|
||||||
|
// it will contain the effective location of the state
|
||||||
|
// directory.
|
||||||
|
//
|
||||||
|
// It should not be changed and is defined as a variable
|
||||||
|
// only for purposes of modification using -X linker flag.
|
||||||
|
DefaultRuntimeDirectory = "/run/maddy"
|
||||||
|
|
||||||
|
// DefaultLibexecDirectory specifies platform-specific
|
||||||
|
// default for LibexecDirectory.
|
||||||
|
//
|
||||||
|
// Most code should use LibexecDirectory since it will
|
||||||
|
// contain the effective location of the libexec
|
||||||
|
// directory.
|
||||||
|
//
|
||||||
|
// It should not be changed and is defined as a variable
|
||||||
|
// only for purposes of modification using -X linker flag.
|
||||||
|
DefaultLibexecDirectory = "/usr/lib/maddy"
|
||||||
|
)
|
11
directories_docker.go
Normal file
11
directories_docker.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//go:build docker
|
||||||
|
// +build docker
|
||||||
|
|
||||||
|
package maddy
|
||||||
|
|
||||||
|
var (
|
||||||
|
ConfigDirectory = "/data"
|
||||||
|
DefaultStateDirectory = "/data"
|
||||||
|
DefaultRuntimeDirectory = "/tmp"
|
||||||
|
DefaultLibexecDirectory = "/usr/lib/maddy"
|
||||||
|
)
|
41
dist/README.md
vendored
Normal file
41
dist/README.md
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Distribution files for maddy
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
**Disclaimer:** Most of the files here are maintained in a "best-effort" way.
|
||||||
|
That is, they may break or become outdated from time to time. Caveat emptor.
|
||||||
|
|
||||||
|
## integration + scripts
|
||||||
|
|
||||||
|
These directories provide pre-made configuration snippets suitable for
|
||||||
|
easy integration with external software.
|
||||||
|
|
||||||
|
Usually, this is what you use when you put `import integration/something` in
|
||||||
|
your config.
|
||||||
|
|
||||||
|
## systemd unit
|
||||||
|
|
||||||
|
`maddy.service` launches using default config path (/etc/maddy/maddy.conf).
|
||||||
|
`maddy@.service` launches maddy using custom config path. E.g.
|
||||||
|
`maddy@foo.service` will use /etc/maddy/foo.conf.
|
||||||
|
|
||||||
|
Additionally, unit files apply strict sandboxing, limiting maddy permissions on
|
||||||
|
the system to a bare minimum. Subset of these options makes it impossible for
|
||||||
|
privileged authentication helper binaries to gain required permissions, so you
|
||||||
|
may have to disable it when using system account-based authentication with
|
||||||
|
maddy running as a unprivileged user.
|
||||||
|
|
||||||
|
## fail2ban configuration
|
||||||
|
|
||||||
|
Configuration files for use with fail2ban. Assume either `backend = systemd` specified
|
||||||
|
in system-wide configuration or log file written to /var/log/maddy/maddy.log.
|
||||||
|
|
||||||
|
See https://github.com/foxcpp/maddy/wiki/fail2ban-configuration for details.
|
||||||
|
|
||||||
|
## logrotate configuration
|
||||||
|
|
||||||
|
Meant for logs rotation when logging to file is used.
|
||||||
|
|
||||||
|
## vim ftdetect/ftplugin/syntax files
|
||||||
|
|
||||||
|
Minimal supplement to make configuration files more readable and help you see
|
||||||
|
typos in directive names.
|
38
dist/apparmor/dev.foxcpp.maddy
vendored
Normal file
38
dist/apparmor/dev.foxcpp.maddy
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# AppArmor profile for maddy daemon.
|
||||||
|
# vim:syntax=apparmor:ts=2:sw=2:et
|
||||||
|
|
||||||
|
#include <tunables/global>
|
||||||
|
|
||||||
|
profile dev.foxcpp.maddy /usr{/local,}/bin/maddy {
|
||||||
|
#include <abstractions/base>
|
||||||
|
#include <abstractions/ssl_certs>
|
||||||
|
#include <abstractions/ssl_keys>
|
||||||
|
/etc/ca-certificates/** r,
|
||||||
|
|
||||||
|
/etc/resolv.conf r,
|
||||||
|
/proc/sys/net/core/somaxconn r,
|
||||||
|
/sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,
|
||||||
|
deny ptrace,
|
||||||
|
capability net_bind_service,
|
||||||
|
network tcp,
|
||||||
|
network unix,
|
||||||
|
|
||||||
|
# systemd process management and Type=notify
|
||||||
|
signal (receive) peer=unconfined,
|
||||||
|
signal (receive) peer=/usr/bin/systemd,
|
||||||
|
unix (create, connect, send, setopt) type=dgram addr=@*,
|
||||||
|
/run/systemd/notify w,
|
||||||
|
|
||||||
|
/etc/maddy/** r,
|
||||||
|
owner /run/maddy/ rw,
|
||||||
|
owner /run/maddy/** rwkl,
|
||||||
|
owner /var/lib/maddy/ rw,
|
||||||
|
owner /var/lib/maddy/** rwk,
|
||||||
|
owner /var/lib/maddy/**.db-{wal,shm} rmk,
|
||||||
|
|
||||||
|
/usr{/local,}/lib/maddy/* PUx,
|
||||||
|
|
||||||
|
/usr{/local,}/bin/maddy{,ctl} rmix,
|
||||||
|
|
||||||
|
#include if exists <local/dev.foxcpp.maddy>
|
||||||
|
}
|
6
dist/fail2ban/filter.d/maddy-auth.conf
vendored
Normal file
6
dist/fail2ban/filter.d/maddy-auth.conf
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[INCLUDES]
|
||||||
|
before = common.conf
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
failregex = authentication failed\t\{\"reason\":\".*\",\"src_ip\"\:\"<HOST>:\d+\"\,\"username\"\:\".*\"\}$
|
||||||
|
journalmatch = _SYSTEMD_UNIT=maddy.service + _COMM=maddy
|
7
dist/fail2ban/filter.d/maddy-dictonary-attack.conf
vendored
Normal file
7
dist/fail2ban/filter.d/maddy-dictonary-attack.conf
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[INCLUDES]
|
||||||
|
before = common.conf
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
failregex = smtp\: MAIL FROM error repeated a lot\, possible dictonary attack\t\{\"count\"\:\d+,\"msg_id\":\".+\",\"src_ip\"\:\"<HOST>:\d+\"\}$
|
||||||
|
smtp\: too many RCPT errors\, possible dictonary attack\t\{\"msg_id\":\".+\","src_ip":"<HOST>:\d+\"\}
|
||||||
|
journalmatch = _SYSTEMD_UNIT=maddy.service + _COMM=maddy
|
5
dist/fail2ban/jail.d/maddy-auth.conf
vendored
Normal file
5
dist/fail2ban/jail.d/maddy-auth.conf
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[maddy-auth]
|
||||||
|
port = 993,465,25
|
||||||
|
filter = maddy-auth
|
||||||
|
bantime = 96h
|
||||||
|
backend = systemd
|
7
dist/fail2ban/jail.d/maddy-dictonary-attack.conf
vendored
Normal file
7
dist/fail2ban/jail.d/maddy-dictonary-attack.conf
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[maddy-dictonary-attack]
|
||||||
|
port = 993,465,25
|
||||||
|
filter = maddy-dictonary-attack
|
||||||
|
bantime = 72h
|
||||||
|
maxretry = 3
|
||||||
|
findtime = 6h
|
||||||
|
backend = systemd
|
24
dist/install.sh
vendored
Executable file
24
dist/install.sh
vendored
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DESTDIR=$DESTDIR
|
||||||
|
if [ -z "$PREFIX" ]; then
|
||||||
|
PREFIX=/usr/local
|
||||||
|
fi
|
||||||
|
if [ -z "$FAIL2BANDIR" ]; then
|
||||||
|
FAIL2BANDIR=/etc/fail2ban
|
||||||
|
fi
|
||||||
|
if [ -z "$CONFDIR" ]; then
|
||||||
|
CONFDIR=/etc/maddy
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
cd $script_dir
|
||||||
|
|
||||||
|
install -Dm 0644 -t "$DESTDIR/$PREFIX/share/vim/vimfiles/ftdetect/" vim/ftdetect/maddy-conf.vim
|
||||||
|
install -Dm 0644 -t "$DESTDIR/$PREFIX/share/vim/vimfiles/ftplugin/" vim/ftplugin/maddy-conf.vim
|
||||||
|
install -Dm 0644 -t "$DESTDIR/$PREFIX/share/vim/vimfiles/syntax/" vim/syntax/maddy-conf.vim
|
||||||
|
|
||||||
|
install -Dm 0644 -t "$DESTDIR/$FAIL2BANDIR/jail.d/" fail2ban/jail.d/*
|
||||||
|
install -Dm 0644 -t "$DESTDIR/$FAIL2BANDIR/filter.d/" fail2ban/filter.d/*
|
||||||
|
|
||||||
|
install -Dm 0644 -t "$DESTDIR/$PREFIX/lib/systemd/system/" systemd/maddy.service systemd/maddy@.service
|
7
dist/logrotate.d/maddy
vendored
Normal file
7
dist/logrotate.d/maddy
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/var/log/maddy/maddy.log {
|
||||||
|
missingok
|
||||||
|
su maddy maddy
|
||||||
|
postrotate
|
||||||
|
/usr/bin/killall -USR1 maddy
|
||||||
|
endscript
|
||||||
|
}
|
82
dist/systemd/maddy.service
vendored
Normal file
82
dist/systemd/maddy.service
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=maddy mail server
|
||||||
|
Documentation=man:maddy(1)
|
||||||
|
Documentation=man:maddy.conf(5)
|
||||||
|
Documentation=https://maddy.email
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=notify
|
||||||
|
NotifyAccess=main
|
||||||
|
|
||||||
|
User=maddy
|
||||||
|
Group=maddy
|
||||||
|
|
||||||
|
# cd to state directory to make sure any relative paths
|
||||||
|
# in config will be relative to it unless handled specially.
|
||||||
|
WorkingDirectory=/var/lib/maddy
|
||||||
|
|
||||||
|
ConfigurationDirectory=maddy
|
||||||
|
RuntimeDirectory=maddy
|
||||||
|
StateDirectory=maddy
|
||||||
|
LogsDirectory=maddy
|
||||||
|
ReadOnlyPaths=/usr/lib/maddy
|
||||||
|
ReadWritePaths=/var/lib/maddy
|
||||||
|
|
||||||
|
# Strict sandboxing. You have no reason to trust code written by strangers from GitHub.
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||||
|
|
||||||
|
# Additional sandboxing. You need to disable all of these options
|
||||||
|
# for privileged helper binaries (for system auth) to work correctly.
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateDevices=true
|
||||||
|
DeviceAllow=/dev/syslog
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
LockPersonality=true
|
||||||
|
|
||||||
|
# Graceful shutdown with a reasonable timeout.
|
||||||
|
TimeoutStopSec=7s
|
||||||
|
KillMode=mixed
|
||||||
|
KillSignal=SIGTERM
|
||||||
|
|
||||||
|
# Required to bind on ports lower than 1024.
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
|
|
||||||
|
# Force all files created by maddy to be only readable by it
|
||||||
|
# and maddy group.
|
||||||
|
UMask=0007
|
||||||
|
|
||||||
|
# Bump FD limitations. Even idle mail server can have a lot of FDs open (think
|
||||||
|
# of idle IMAP connections, especially ones abandoned on the other end and
|
||||||
|
# slowly timing out).
|
||||||
|
LimitNOFILE=131072
|
||||||
|
|
||||||
|
# Limit processes count to something reasonable to
|
||||||
|
# prevent resources exhausting due to big amounts of helper
|
||||||
|
# processes launched.
|
||||||
|
LimitNPROC=512
|
||||||
|
|
||||||
|
# Restart server on any problem.
|
||||||
|
Restart=on-failure
|
||||||
|
# ... Unless it is a configuration problem.
|
||||||
|
RestartPreventExitStatus=2
|
||||||
|
|
||||||
|
ExecStart=/usr/local/bin/maddy run
|
||||||
|
|
||||||
|
ExecReload=/bin/kill -USR1 $MAINPID
|
||||||
|
ExecReload=/bin/kill -USR2 $MAINPID
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
78
dist/systemd/maddy@.service
vendored
Normal file
78
dist/systemd/maddy@.service
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=maddy mail server (using %i.conf)
|
||||||
|
Documentation=man:maddy(1)
|
||||||
|
Documentation=man:maddy.conf(5)
|
||||||
|
Documentation=https://maddy.email
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=notify
|
||||||
|
NotifyAccess=main
|
||||||
|
|
||||||
|
User=maddy
|
||||||
|
Group=maddy
|
||||||
|
|
||||||
|
ConfigurationDirectory=maddy
|
||||||
|
RuntimeDirectory=maddy
|
||||||
|
StateDirectory=maddy
|
||||||
|
LogsDirectory=maddy
|
||||||
|
ReadOnlyPaths=/usr/lib/maddy
|
||||||
|
ReadWritePaths=/var/lib/maddy
|
||||||
|
|
||||||
|
# Strict sandboxing. You have no reason to trust code written by strangers from GitHub.
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateHome=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||||
|
DeviceAllow=/dev/syslog
|
||||||
|
|
||||||
|
# Additional sandboxing. You need to disable all of these options
|
||||||
|
# for privileged helper binaries (for system auth) to work correctly.
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateDevices=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
LockPersonality=true
|
||||||
|
|
||||||
|
# Graceful shutdown with a reasonable timeout.
|
||||||
|
TimeoutStopSec=7s
|
||||||
|
KillMode=mixed
|
||||||
|
KillSignal=SIGTERM
|
||||||
|
|
||||||
|
# Required to bind on ports lower than 1024.
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
|
|
||||||
|
# Force all files created by maddy to be only readable by it and
|
||||||
|
# maddy group.
|
||||||
|
UMask=0007
|
||||||
|
|
||||||
|
# Bump FD limitations. Even idle mail server can have a lot of FDs open (think
|
||||||
|
# of idle IMAP connections, especially ones abandoned on the other end and
|
||||||
|
# slowly timing out).
|
||||||
|
LimitNOFILE=131072
|
||||||
|
|
||||||
|
# Limit processes count to something reasonable to
|
||||||
|
# prevent resources exhausting due to big amounts of helper
|
||||||
|
# processes launched.
|
||||||
|
LimitNPROC=512
|
||||||
|
|
||||||
|
# Restart server on any problem.
|
||||||
|
Restart=on-failure
|
||||||
|
# ... Unless it is a configuration problem.
|
||||||
|
RestartPreventExitStatus=2
|
||||||
|
|
||||||
|
ExecStart=/usr/local/bin/maddy --config /etc/maddy/%i.conf run
|
||||||
|
|
||||||
|
ExecReload=/bin/kill -USR1 $MAINPID
|
||||||
|
ExecReload=/bin/kill -USR2 $MAINPID
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
1
dist/vim/ftdetect/maddy-conf.vim
vendored
Normal file
1
dist/vim/ftdetect/maddy-conf.vim
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
au BufNewFile,BufRead /etc/maddy/*,maddy.conf setf maddy-conf
|
8
dist/vim/ftplugin/maddy-conf.vim
vendored
Normal file
8
dist/vim/ftplugin/maddy-conf.vim
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
setlocal commentstring=#\ %s
|
||||||
|
|
||||||
|
" That is convention for maddy configs. Period.
|
||||||
|
" - fox.cpp (maddy developer)
|
||||||
|
setlocal expandtab
|
||||||
|
setlocal tabstop=4
|
||||||
|
setlocal softtabstop=4
|
||||||
|
setlocal shiftwidth=4
|
225
dist/vim/syntax/maddy-conf.vim
vendored
Normal file
225
dist/vim/syntax/maddy-conf.vim
vendored
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
" vim: noexpandtab ts=4 sw=4
|
||||||
|
|
||||||
|
if exists("b:current_syntax")
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
" Lexer-defined rules
|
||||||
|
syn match maddyComment "#.*"
|
||||||
|
syn region maddyString start=+"+ skip=+\\\\\|\\"+ end=+"+ oneline
|
||||||
|
|
||||||
|
syn region maddyBlock start="{" end="}" transparent fold
|
||||||
|
|
||||||
|
hi def link maddyComment Comment
|
||||||
|
hi def link maddyString String
|
||||||
|
|
||||||
|
" Parser-defined rules
|
||||||
|
syn match maddyMacroName "[a-z0-9_]" contained containedin=maddyMacro
|
||||||
|
syn match maddyMacro "$(.\{-})" contains=maddyMacroName
|
||||||
|
|
||||||
|
syn match maddyMacroDefSign "=" contained
|
||||||
|
syn match maddyMacroDef "\^$([a-z0-9_]\{-})\s=\s.\+" contains=maddyMacro,maddyMacroDefSign
|
||||||
|
|
||||||
|
hi def link maddyMacroName Identifier
|
||||||
|
hi def link maddyMacro Special
|
||||||
|
hi def link maddyMacroDefSign Special
|
||||||
|
|
||||||
|
" config.Map values
|
||||||
|
syn keyword maddyBool yes no
|
||||||
|
|
||||||
|
syn match maddyInt '\<\d\+\>'
|
||||||
|
syn match maddyInt '\<[-+]\d\+\>'
|
||||||
|
syn match maddyFloat '\<[-+]\d\+\.\d*\<'
|
||||||
|
|
||||||
|
syn match maddyReference /[ \t]&[^ \t]\+/ms=s+1 contains=maddyReferenceSign
|
||||||
|
syn match maddyReferenceSign /&/ contained
|
||||||
|
|
||||||
|
hi def link maddyBool Boolean
|
||||||
|
hi def link maddyInt Number
|
||||||
|
hi def link maddyFloat Float
|
||||||
|
|
||||||
|
hi def link maddyReferenceSign Special
|
||||||
|
|
||||||
|
" Module values
|
||||||
|
|
||||||
|
" grep --no-file -E 'Register.*\(".+", ' **.go | sed -E 's/.+Register.*\("([^"]+)", .+/\1/' | sort -u
|
||||||
|
syn keyword maddyModule
|
||||||
|
\ checks
|
||||||
|
\ command
|
||||||
|
\ dane
|
||||||
|
\ dkim
|
||||||
|
\ dnsbl
|
||||||
|
\ dnssec
|
||||||
|
\ dummy
|
||||||
|
\ extauth
|
||||||
|
\ external
|
||||||
|
\ file
|
||||||
|
\ identity
|
||||||
|
\ imap
|
||||||
|
\ imap_filters
|
||||||
|
\ imapsql
|
||||||
|
\ limits
|
||||||
|
\ lmtp
|
||||||
|
\ loader
|
||||||
|
\ local_policy
|
||||||
|
\ milter
|
||||||
|
\ modifiers
|
||||||
|
\ msgpipeline
|
||||||
|
\ mtasts
|
||||||
|
\ mx_auth
|
||||||
|
\ pam
|
||||||
|
\ pass_table
|
||||||
|
\ plain_separate
|
||||||
|
\ queue
|
||||||
|
\ regexp
|
||||||
|
\ remote
|
||||||
|
\ replace_rcpt
|
||||||
|
\ replace_sender
|
||||||
|
\ require_matching_rdns
|
||||||
|
\ require_mx_record
|
||||||
|
\ require_tls
|
||||||
|
\ rspamd
|
||||||
|
\ shadow
|
||||||
|
\ smtp
|
||||||
|
\ sql_query
|
||||||
|
\ sql_table
|
||||||
|
\ static
|
||||||
|
\ submission
|
||||||
|
|
||||||
|
syn keyword maddyDispatchDir
|
||||||
|
\ check
|
||||||
|
\ modify
|
||||||
|
\ default_source
|
||||||
|
\ source
|
||||||
|
\ default_destination
|
||||||
|
\ destination
|
||||||
|
\ reject
|
||||||
|
\ deliver_to
|
||||||
|
\ reroute
|
||||||
|
\ dmarc
|
||||||
|
|
||||||
|
" grep --no-file -E 'cfg..+\(".+", ' **.go | sed -E 's/.+cfg..+\("([^"]+)", .+/\1/' | sort -u
|
||||||
|
syn keyword maddyModDir
|
||||||
|
\ add
|
||||||
|
\ add_header_action
|
||||||
|
\ allow_multiple_from
|
||||||
|
\ api_path
|
||||||
|
\ appendlimit
|
||||||
|
\ attempt_starttls
|
||||||
|
\ auth
|
||||||
|
\ autogenerated_msg_domain
|
||||||
|
\ body_canon
|
||||||
|
\ bounce
|
||||||
|
\ broken_sig_action
|
||||||
|
\ buffer
|
||||||
|
\ cache
|
||||||
|
\ case_insensitive
|
||||||
|
\ certs
|
||||||
|
\ check_early
|
||||||
|
\ client_ipv4
|
||||||
|
\ client_ipv6
|
||||||
|
\ compression
|
||||||
|
\ conn_max_idle_count
|
||||||
|
\ conn_max_idle_time
|
||||||
|
\ conn_reuse_limit
|
||||||
|
\ debug
|
||||||
|
\ defer_sender_reject
|
||||||
|
\ del
|
||||||
|
\ domains
|
||||||
|
\ driver
|
||||||
|
\ dsn
|
||||||
|
\ ehlo
|
||||||
|
\ endpoint
|
||||||
|
\ enforce_early
|
||||||
|
\ enforce_testing
|
||||||
|
\ entry
|
||||||
|
\ error_resp_action
|
||||||
|
\ expand_replaceholders
|
||||||
|
\ fail_action
|
||||||
|
\ fail_open
|
||||||
|
\ file
|
||||||
|
\ flags
|
||||||
|
\ force_ipv4
|
||||||
|
\ fs_dir
|
||||||
|
\ fsstore
|
||||||
|
\ full_match
|
||||||
|
\ hash
|
||||||
|
\ header_canon
|
||||||
|
\ helper
|
||||||
|
\ hostname
|
||||||
|
\ imap_filter
|
||||||
|
\ init
|
||||||
|
\ insecure_auth
|
||||||
|
\ io_debug
|
||||||
|
\ io_error_action
|
||||||
|
\ io_errors
|
||||||
|
\ junk_mailbox
|
||||||
|
\ key_column
|
||||||
|
\ key_path
|
||||||
|
\ keys
|
||||||
|
\ limits
|
||||||
|
\ list
|
||||||
|
\ local_ip
|
||||||
|
\ location
|
||||||
|
\ lookup
|
||||||
|
\ mailfrom
|
||||||
|
\ max_logged_rcpt_errors
|
||||||
|
\ max_message_size
|
||||||
|
\ max_parallelism
|
||||||
|
\ max_received
|
||||||
|
\ max_recipients
|
||||||
|
\ max_tries
|
||||||
|
\ min_mx_level
|
||||||
|
\ min_tls_level
|
||||||
|
\ mx_auth
|
||||||
|
\ neutral_action
|
||||||
|
\ newkey_algo
|
||||||
|
\ none_action
|
||||||
|
\ no_sig_action
|
||||||
|
\ oversign_fields
|
||||||
|
\ pass
|
||||||
|
\ perdomain
|
||||||
|
\ permerr_action
|
||||||
|
\ quarantine_threshold
|
||||||
|
\ read_timeout
|
||||||
|
\ reject_threshold
|
||||||
|
\ relaxed_requiretls
|
||||||
|
\ required_fields
|
||||||
|
\ require_sender_match
|
||||||
|
\ require_tls
|
||||||
|
\ requiretls_override
|
||||||
|
\ responses
|
||||||
|
\ rewrite_subj_action
|
||||||
|
\ run_on
|
||||||
|
\ score
|
||||||
|
\ selector
|
||||||
|
\ set
|
||||||
|
\ settings_id
|
||||||
|
\ sig_expiry
|
||||||
|
\ sign_fields
|
||||||
|
\ sign_subdomains
|
||||||
|
\ softfail_action
|
||||||
|
\ SOME_action
|
||||||
|
\ source
|
||||||
|
\ sqlite3_busy_timeout
|
||||||
|
\ sqlite3_cache_size
|
||||||
|
\ sqlite3_exclusive_lock
|
||||||
|
\ storage
|
||||||
|
\ table
|
||||||
|
\ table_name
|
||||||
|
\ tag
|
||||||
|
\ target
|
||||||
|
\ targets
|
||||||
|
\ temperr_action
|
||||||
|
\ tls
|
||||||
|
\ tls_client
|
||||||
|
\ use_helper
|
||||||
|
\ user
|
||||||
|
\ value_column
|
||||||
|
\ write_timeout
|
||||||
|
|
||||||
|
hi def link maddyModDir Identifier
|
||||||
|
hi def link maddyModule Identifier
|
||||||
|
hi def link maddyDispatchDir Identifier
|
||||||
|
|
||||||
|
let b:current_syntax = "maddy"
|
81
docs/docker.md
Normal file
81
docs/docker.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Docker
|
||||||
|
|
||||||
|
Official Docker image is available from Docker Hub.
|
||||||
|
|
||||||
|
It expects configuration file to be available at /data/maddy.conf.
|
||||||
|
|
||||||
|
If /data is a Docker volume, then default configuration will be placed there
|
||||||
|
automatically. If it is used, then MADDY_HOSTNAME, MADDY_DOMAIN environment
|
||||||
|
variables control the host name and primary domain for the server. TLS
|
||||||
|
certificate should be placed in /data/tls/fullchain.pem, private key in
|
||||||
|
/data/tls/privkey.pem
|
||||||
|
|
||||||
|
DKIM keys are generated in /data/dkim_keys directory.
|
||||||
|
|
||||||
|
## Image tags
|
||||||
|
|
||||||
|
- `latest` - A latest stable release. May contain breaking changes.
|
||||||
|
- `X.Y` - A specific feature branch, it is recommended to use these tags to
|
||||||
|
receive bugfixes without the risk of feature-related regressions or breaking
|
||||||
|
changes.
|
||||||
|
- `X.Y.Z` - A specific stable release
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
All standard ports, as described in maddy docs.
|
||||||
|
|
||||||
|
- `25` - SMTP inbound port.
|
||||||
|
- `465`, `587` - SMTP Submission ports
|
||||||
|
- `993`, `143` - IMAP4 ports
|
||||||
|
|
||||||
|
## Volumes
|
||||||
|
|
||||||
|
`/data` - maddy state directory. Databases, queues, etc are stored here. You
|
||||||
|
might want to mount a named volume there. The main configuration file is stored
|
||||||
|
here too (`/data/maddy.conf`).
|
||||||
|
|
||||||
|
## Management utility
|
||||||
|
|
||||||
|
To run management commands, create a temporary container with the same
|
||||||
|
/data directory and put the command after the image name, like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --rm -it -v maddydata:/data foxcpp/maddy:0.7 creds create foxcpp@maddy.test
|
||||||
|
docker run --rm -it -v maddydata:/data foxcpp/maddy:0.7 imap-acct create foxcpp@maddy.test
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the same image version as the running server. Things may break badly
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
Note that, if you modify messages using maddy subcommands while the server is running -
|
||||||
|
you must ensure that /tmp from the server is accessible for the management
|
||||||
|
command. One way to it is to run it using `docker exec` instead of `docker run`:
|
||||||
|
```
|
||||||
|
docker exec -it container_name_here maddy creds create foxcpp@maddy.test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Tags
|
||||||
|
|
||||||
|
Some Maddy features (such as automatic certificate management via ACME with [a non-default libdns provider](../reference/tls-acme/#dns-providers)) require build tags to be passed to Maddy's `build.sh`, as this is run in the Dockerfile you must compile your own Docker image. Build tags can be set via the docker build argument `ADDITIONAL_BUILD_TAGS` e.g. `docker build --build-arg ADDITIONAL_BUILD_TAGS="libdns_acmedns libdns_route53" -t yourorgname/maddy:yourtagname .`.
|
||||||
|
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
```
|
||||||
|
docker volume create maddydata
|
||||||
|
docker run \
|
||||||
|
--name maddy \
|
||||||
|
-e MADDY_HOSTNAME=mx.maddy.test \
|
||||||
|
-e MADDY_DOMAIN=maddy.test \
|
||||||
|
-v maddydata:/data \
|
||||||
|
-p 25:25 \
|
||||||
|
-p 143:143 \
|
||||||
|
-p 465:465 \
|
||||||
|
-p 587:587 \
|
||||||
|
-p 993:993 \
|
||||||
|
foxcpp/maddy:0.7
|
||||||
|
```
|
||||||
|
|
||||||
|
It will fail on first startup. Copy TLS certificate to /data/tls/fullchain.pem
|
||||||
|
and key to /data/tls/privkey.pem. Run the server again. Finish DNS configuration
|
||||||
|
(DKIM keys, etc) as described in [tutorials/setting-up/](../tutorials/setting-up/).
|
119
docs/faq.md
Normal file
119
docs/faq.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# Frequently Asked Questions
|
||||||
|
|
||||||
|
## I configured maddy as recommended and gmail still puts my messages in spam
|
||||||
|
|
||||||
|
Unfortunately, GMail policies are opaque so we cannot tell why this happens.
|
||||||
|
|
||||||
|
Verify that you have a rDNS record set for the IP used
|
||||||
|
by sender server. Also some IPs may just happen to
|
||||||
|
have bad reputation - check it with various DNSBLs. In this
|
||||||
|
case you do not have much of a choice but to replace it.
|
||||||
|
|
||||||
|
Additionally, you may try marking multiple messages sent from
|
||||||
|
your domain as "not spam" in GMail UI.
|
||||||
|
|
||||||
|
## Message sending fails with `dial tcp X.X.X.X:25: connect: connection timed out` in log
|
||||||
|
|
||||||
|
Your provider is blocking outbound SMTP traffic on port 25.
|
||||||
|
|
||||||
|
You either have to ask them to unblock it or forward
|
||||||
|
all outbound messages via a "smart-host".
|
||||||
|
|
||||||
|
## What is resource usage of maddy?
|
||||||
|
|
||||||
|
For a small personal server, you do not need much more than a
|
||||||
|
single 1 GiB of RAM and disk space.
|
||||||
|
|
||||||
|
## How to setup a catchall address?
|
||||||
|
|
||||||
|
https://github.com/foxcpp/maddy/issues/243#issuecomment-655694512
|
||||||
|
|
||||||
|
## maddy command prints a "permission denied" error
|
||||||
|
|
||||||
|
Run maddy command under the same user as maddy itself.
|
||||||
|
E.g.
|
||||||
|
```
|
||||||
|
sudo -u maddy maddy creds ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## How maddy compares to MailCow or Mail-In-The-Box?
|
||||||
|
|
||||||
|
MailCow and MIAB are bundles of well-known email-related software configured to
|
||||||
|
work together. maddy is a single piece of software implementing subset of what
|
||||||
|
MailCow and MIAB offer.
|
||||||
|
|
||||||
|
maddy offers more uniform configuration system, more lightweight implementation
|
||||||
|
and has no dependency on Docker or similar technologies for deployment.
|
||||||
|
|
||||||
|
maddy may have more bugs than 20 years old battle-tested software.
|
||||||
|
|
||||||
|
It is easier to get help with MailCow/MITB since underlying implementations
|
||||||
|
are well-understood and have active community.
|
||||||
|
|
||||||
|
maddy has no Web interface for administration, that is currently done via CLI
|
||||||
|
utility.
|
||||||
|
|
||||||
|
## How maddy IMAP server compares to WildDuck?
|
||||||
|
|
||||||
|
Both are "more secure by definition": root access is not required,
|
||||||
|
implementation is in memory-safe language, etc.
|
||||||
|
|
||||||
|
Both support message compression.
|
||||||
|
|
||||||
|
Both have first-class Unicode/internationalization support.
|
||||||
|
|
||||||
|
WildDuck may offer easier scalability options. maddy does not require you to
|
||||||
|
setup MongoDB and Redis servers, though. In fact, maddy in its default
|
||||||
|
configuration has no dependencies besides libc.
|
||||||
|
|
||||||
|
maddy has less builtin authentication providers. This means no
|
||||||
|
app-specific passwords and all that WildDuck lists under point 4 on their
|
||||||
|
features page.
|
||||||
|
|
||||||
|
maddy currently has no admin Web interface, all necessary DB changes are
|
||||||
|
performed via CLI utility.
|
||||||
|
|
||||||
|
## How maddy SMTP server compares to ZoneMTA?
|
||||||
|
|
||||||
|
maddy SMTP server has a lot of similarities to ZoneMTA.
|
||||||
|
Both have powerful mechanisms for message routing (although designed
|
||||||
|
differently).
|
||||||
|
|
||||||
|
maddy does not require MongoDB server for deployment.
|
||||||
|
|
||||||
|
maddy has no web interface for queue inspection. However, it can
|
||||||
|
easily inspected by looking at files in /var/lib/maddy.
|
||||||
|
|
||||||
|
ZoneMTA has a number of features that may make it easier to integrate
|
||||||
|
with HTTP-based services. maddy speaks standard email protocols (SMTP,
|
||||||
|
Submission).
|
||||||
|
|
||||||
|
## Is there a webmail?
|
||||||
|
|
||||||
|
No, at least currently.
|
||||||
|
|
||||||
|
I suggest you to check out [alps](https://git.sr.ht/~migadu/alps) if you
|
||||||
|
are fine with alpha-quality but extremely easy to deploy webmail.
|
||||||
|
|
||||||
|
## Is there a content filter (spam filter)?
|
||||||
|
|
||||||
|
No. maddy moves email messages around, it does not classify
|
||||||
|
them as bad or good with the notable exception of sender policies.
|
||||||
|
|
||||||
|
It is possible to integrate rspamd using 'rspamd' module. Just add
|
||||||
|
`rspamd` line to `checks` in `local_routing`, it should just work
|
||||||
|
in most cases.
|
||||||
|
|
||||||
|
## Is it production-ready?
|
||||||
|
|
||||||
|
maddy is considered "beta" quality. Several people use it for personal email.
|
||||||
|
|
||||||
|
## Single process makes it unreliable. This is dumb!
|
||||||
|
|
||||||
|
This is a compromise between ease of management and reliability. Several
|
||||||
|
measures are implemented in code base in attempt to reduce possible effect
|
||||||
|
of bugs in one component.
|
||||||
|
|
||||||
|
Besides, you are not required to use a single process, it is easy to launch
|
||||||
|
maddy with a non-default configuration path and connect multiple instances
|
||||||
|
together using off-the-shelf protocols.
|
22
docs/index.md
Normal file
22
docs/index.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
> Composable all-in-one mail server.
|
||||||
|
|
||||||
|
Maddy Mail Server implements all functionality required to run a e-mail
|
||||||
|
server. It can send messages via SMTP (works as MTA), accept messages via SMTP
|
||||||
|
(works as MX) and store messages while providing access to them via IMAP.
|
||||||
|
In addition to that it implements auxiliary protocols that are mandatory
|
||||||
|
to keep email reasonably secure (DKIM, SPF, DMARC, DANE, MTA-STS).
|
||||||
|
|
||||||
|
It replaces Postfix, Dovecot, OpenDKIM, OpenSPF, OpenDMARC and more with one
|
||||||
|
daemon with uniform configuration and minimal maintenance cost.
|
||||||
|
|
||||||
|
**Note:** IMAP storage is "beta". If you are looking for stable and
|
||||||
|
feature-packed implementation you may want to use Dovecot instead. maddy still
|
||||||
|
can handle message delivery business.
|
||||||
|
|
||||||
|
[](https://builds.sr.ht/~emersion/maddy?)
|
||||||
|
[](https://github.com/foxcpp/maddy/blob/master/LICENSE)
|
||||||
|
[](https://github.com/foxcpp/maddy)
|
||||||
|
|
||||||
|
* [Setup tutorial](https://maddy.email/tutorials/setting-up/)
|
||||||
|
* [IRC channel](https://webchat.oftc.net/?channels=maddy&uio=MT11bmRlZmluZWQb1)
|
||||||
|
* [Mailing list](https://lists.sr.ht/~foxcpp/maddy)
|
23
docs/internals/quirks.md
Normal file
23
docs/internals/quirks.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Implementation quirks
|
||||||
|
|
||||||
|
This page documents unusual behavior of the maddy protocols implementations.
|
||||||
|
Some of these problems break standards, some don't but still can hurt
|
||||||
|
interoperability.
|
||||||
|
|
||||||
|
## SMTP
|
||||||
|
|
||||||
|
- `for` field is never included in the `Received` header field.
|
||||||
|
|
||||||
|
This is allowed by [RFC 2821].
|
||||||
|
|
||||||
|
## IMAP
|
||||||
|
|
||||||
|
### `sql`
|
||||||
|
|
||||||
|
- `\Recent` flag is not reset in all cases.
|
||||||
|
|
||||||
|
This _does not_ break [RFC 3501]. Clients relying on it will work (much) less
|
||||||
|
efficiently.
|
||||||
|
|
||||||
|
[RFC 2821]: https://tools.ietf.org/html/rfc2821
|
||||||
|
[RFC 3501]: https://tools.ietf.org/html/rfc3501
|
291
docs/internals/specifications.md
Normal file
291
docs/internals/specifications.md
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
# Followed specifications
|
||||||
|
|
||||||
|
This page lists Internet Standards and other specifications followed by
|
||||||
|
maddy along with any known deviations.
|
||||||
|
|
||||||
|
|
||||||
|
## Message format
|
||||||
|
|
||||||
|
- [RFC 2822] - Internet Message Format
|
||||||
|
- [RFC 2045] - Multipurpose Internet Mail Extensions (MIME) (part 1)
|
||||||
|
- [RFC 2046] - Multipurpose Internet Mail Extensions (MIME) (part 2)
|
||||||
|
- [RFC 2047] - Multipurpose Internet Mail Extensions (MIME) (part 3)
|
||||||
|
- [RFC 2048] - Multipurpose Internet Mail Extensions (MIME) (part 4)
|
||||||
|
- [RFC 2049] - Multipurpose Internet Mail Extensions (MIME) (part 5)
|
||||||
|
- [RFC 6532] - Internationalized Email Headers
|
||||||
|
|
||||||
|
- [RFC 2183] - Communicating Presentation Information in Internet Messages: The
|
||||||
|
Content-Disposition Header Field
|
||||||
|
|
||||||
|
## IMAP
|
||||||
|
|
||||||
|
- [RFC 3501] - Internet Message Access Protocol - Version 4rev1
|
||||||
|
* **Partial**: `\Recent` flag is not reset sometimes.
|
||||||
|
- [RFC 2152] - UTF-7
|
||||||
|
|
||||||
|
### Extensions
|
||||||
|
|
||||||
|
- [RFC 2595] - Using TLS with IMAP, POP3 and ACAP
|
||||||
|
- [RFC 7889] - The IMAP APPENDLIMIT Extension
|
||||||
|
- [RFC 3348] - The Internet Message Action Protocol (IMAP4). Child Mailbox
|
||||||
|
Extension
|
||||||
|
- [RFC 6851] - Internet Message Access Protocol (IMAP) - MOVE Extension
|
||||||
|
- [RFC 6154] - IMAP LIST Extension for Special-Use Mailboxes
|
||||||
|
* **Partial**: Only SPECIAL-USE capability.
|
||||||
|
- [RFC 5255] - Internet Message Access Protocol Internationalization
|
||||||
|
* **Partial**: Only I18NLEVEL=1 capability.
|
||||||
|
- [RFC 4978] - The IMAP COMPRESS Extension
|
||||||
|
- [RFC 3691] - Internet Message Access Protocol (IMAP) UNSELECT command
|
||||||
|
- [RFC 2177] - IMAP4 IDLE command
|
||||||
|
- [RFC 7888] - IMAP4 Non-Synchronizing Literals
|
||||||
|
* LITERAL+ capability.
|
||||||
|
- [RFC 4959] - IMAP Extension for Simple Authentication and Security Layer
|
||||||
|
(SASL) Initial Client Response
|
||||||
|
|
||||||
|
## SMTP
|
||||||
|
|
||||||
|
- [RFC 2033] - Local Mail Transfer Protocol
|
||||||
|
- [RFC 5321] - Simple Mail Transfer Protocol
|
||||||
|
- [RFC 6409] - Message Submission for Mail
|
||||||
|
|
||||||
|
### Extensions
|
||||||
|
|
||||||
|
- [RFC 1870] - SMTP Service Extension for Message Size Declaration
|
||||||
|
- [RFC 2920] - SMTP Service Extension for Command Pipelining
|
||||||
|
* Server support only, not used by SMTP client
|
||||||
|
- [RFC 2034] - SMTP Service Extension for Returning Enhanced Error Codes
|
||||||
|
- [RFC 3207] - SMTP Service Extension for Secure SMTP over Transport Layer
|
||||||
|
Security
|
||||||
|
- [RFC 4954] - SMTP Service Extension for Authentication
|
||||||
|
- [RFC 6152] - SMTP Extension for 8-bit MIME
|
||||||
|
- [RFC 6531] - SMTP Extension for Internationalized Email
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
- [RFC 6522] - The Multipart/Report Content Type for the Reporting of Mail
|
||||||
|
System Administrative Messages
|
||||||
|
- [RFC 3464] - An Extensible Message Format for Delivery Status Notifications
|
||||||
|
- [RFC 6533] - Internationalized Delivery Status and Disposition Notifications
|
||||||
|
|
||||||
|
## Email security
|
||||||
|
|
||||||
|
### User authentication
|
||||||
|
|
||||||
|
- [RFC 4422] - Simple Authentication and Security Layer (SASL)
|
||||||
|
- [RFC 4616] - The PLAIN Simple Authentication and Security Layer (SASL)
|
||||||
|
Mechanism
|
||||||
|
|
||||||
|
### Sender authentication
|
||||||
|
|
||||||
|
- [RFC 6376] - DomainKeys Identified Mail (DKIM) Signatures
|
||||||
|
- [RFC 7001] - Message Header Field for Indicating Message Authentication Status
|
||||||
|
- [RFC 7208] - Sender Policy Framework (SPF) for Authorizing Use of Domains in
|
||||||
|
Email, Version 1
|
||||||
|
- [RFC 7372] - Email Authentication Status Codes
|
||||||
|
- [RFC 7479] - Domain-based Message Authentication, Reporting, and Conformance
|
||||||
|
(DMARC)
|
||||||
|
* **Partial**: No report generation.
|
||||||
|
- [RFC 8301] - Cryptographic Algorithm and Key Usage Update to DomainKeys
|
||||||
|
Identified Mail (DKIM)
|
||||||
|
- [RFC 8463] - A New Cryptographic Signature Method for DomainKeys Identified
|
||||||
|
Mail (DKIM)
|
||||||
|
- [RFC 8616] - Email Authentication for Internationalized Mail
|
||||||
|
|
||||||
|
### Recipient authentication
|
||||||
|
|
||||||
|
- [RFC 4033] - DNS Security Introduction and Requirements
|
||||||
|
- [RFC 6698] - The DNS-Based Authentication of Named Entities (DANE) Transport
|
||||||
|
Layer Security (TLS) Protocol: TLSA
|
||||||
|
- [RFC 7672] - SMTP Security via Opportunistic DNS-Based Authentication of
|
||||||
|
Named Entities (DANE) Transport Layer Security (TLS)
|
||||||
|
- [RFC 8461] - SMTP MTA Strict Transport Security (MTA-STS)
|
||||||
|
|
||||||
|
## Unicode, encodings, internationalization
|
||||||
|
|
||||||
|
- [RFC 3492] - Punycode: A Bootstring encoding of Unicode for Internationalized
|
||||||
|
Domain Names in Applications (IDNA)
|
||||||
|
- [RFC 3629] - UTF-8, a transformation format of ISO 10646
|
||||||
|
- [RFC 5890] - Internationalized Domain Names for Applications (IDNA):
|
||||||
|
Definitions and Document Framework
|
||||||
|
- [RFC 5891] - Internationalized Domain Names for Applications (IDNA): Protocol
|
||||||
|
- [RFC 7616] - Preparation, Enforcement, and Comparison of Internationalized
|
||||||
|
Strings Representing Usernames and Passwords
|
||||||
|
- [RFC 8264] - PRECIS Framework: Preparation, Enforcement, and Comparison of
|
||||||
|
Internationalized Strings in Application Protocols
|
||||||
|
- [Unicode 11.0.0]
|
||||||
|
- [UAX #15] - Unicode Normalization Forms
|
||||||
|
|
||||||
|
There is a huge list of non-Unicode encodings supported by message parser used
|
||||||
|
for IMAP static cache and search. See [Unicode support](unicode.md) page for
|
||||||
|
details.
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
|
- [RFC 5782] - DNS Blacklists and Whitelists
|
||||||
|
|
||||||
|
|
||||||
|
[GH 188]: https://github.com/foxcpp/maddy/issues/188
|
||||||
|
|
||||||
|
[RFC 2822]: https://tools.ietf.org/html/rfc2822
|
||||||
|
[RFC 2045]: https://tools.ietf.org/html/rfc2045
|
||||||
|
[RFC 2046]: https://tools.ietf.org/html/rfc2046
|
||||||
|
[RFC 2047]: https://tools.ietf.org/html/rfc2047
|
||||||
|
[RFC 2048]: https://tools.ietf.org/html/rfc2048
|
||||||
|
[RFC 2049]: https://tools.ietf.org/html/rfc2049
|
||||||
|
[RFC 6532]: https://tools.ietf.org/html/rfc6532
|
||||||
|
[RFC 2183]: https://tools.ietf.org/html/rfc2183
|
||||||
|
[RFC 3501]: https://tools.ietf.org/html/rfc3501
|
||||||
|
[RFC 2152]: https://tools.ietf.org/html/rfc2152
|
||||||
|
[RFC 2595]: https://tools.ietf.org/html/rfc2595
|
||||||
|
[RFC 7889]: https://tools.ietf.org/html/rfc7889
|
||||||
|
[RFC 3348]: https://tools.ietf.org/html/rfc3348
|
||||||
|
[RFC 6851]: https://tools.ietf.org/html/rfc6851
|
||||||
|
[RFC 6154]: https://tools.ietf.org/html/rfc6154
|
||||||
|
[RFC 5255]: https://tools.ietf.org/html/rfc5255
|
||||||
|
[RFC 4978]: https://tools.ietf.org/html/rfc4978
|
||||||
|
[RFC 3691]: https://tools.ietf.org/html/rfc3691
|
||||||
|
[RFC 2177]: https://tools.ietf.org/html/rfc2177
|
||||||
|
[RFC 7888]: https://tools.ietf.org/html/rfc7888
|
||||||
|
[RFC 4959]: https://tools.ietf.org/html/rfc4959
|
||||||
|
[RFC 2033]: https://tools.ietf.org/html/rfc2033
|
||||||
|
[RFC 5321]: https://tools.ietf.org/html/rfc5321
|
||||||
|
[RFC 6409]: https://tools.ietf.org/html/rfc6409
|
||||||
|
[RFC 1870]: https://tools.ietf.org/html/rfc1870
|
||||||
|
[RFC 2920]: https://tools.ietf.org/html/rfc2920
|
||||||
|
[RFC 2034]: https://tools.ietf.org/html/rfc2034
|
||||||
|
[RFC 3207]: https://tools.ietf.org/html/rfc3207
|
||||||
|
[RFC 4954]: https://tools.ietf.org/html/rfc4954
|
||||||
|
[RFC 6152]: https://tools.ietf.org/html/rfc6152
|
||||||
|
[RFC 6531]: https://tools.ietf.org/html/rfc6531
|
||||||
|
[RFC 6522]: https://tools.ietf.org/html/rfc6522
|
||||||
|
[RFC 3464]: https://tools.ietf.org/html/rfc3464
|
||||||
|
[RFC 6533]: https://tools.ietf.org/html/rfc6533
|
||||||
|
[RFC 4422]: https://tools.ietf.org/html/rfc4422
|
||||||
|
[RFC 4616]: https://tools.ietf.org/html/rfc4616
|
||||||
|
[RFC 6376]: https://tools.ietf.org/html/rfc6376
|
||||||
|
[RFC 7001]: https://tools.ietf.org/html/rfc7001
|
||||||
|
[RFC 7208]: https://tools.ietf.org/html/rfc7208
|
||||||
|
[RFC 7372]: https://tools.ietf.org/html/rfc7372
|
||||||
|
[RFC 7479]: https://tools.ietf.org/html/rfc7479
|
||||||
|
[RFC 8301]: https://tools.ietf.org/html/rfc8301
|
||||||
|
[RFC 8463]: https://tools.ietf.org/html/rfc8463
|
||||||
|
[RFC 8616]: https://tools.ietf.org/html/rfc8616
|
||||||
|
[RFC 4033]: https://tools.ietf.org/html/rfc4033
|
||||||
|
[RFC 6698]: https://tools.ietf.org/html/rfc6698
|
||||||
|
[RFC 7672]: https://tools.ietf.org/html/rfc7672
|
||||||
|
[RFC 8461]: https://tools.ietf.org/html/rfc8461
|
||||||
|
[RFC 3492]: https://tools.ietf.org/html/rfc3492
|
||||||
|
[RFC 3629]: https://tools.ietf.org/html/rfc3629
|
||||||
|
[RFC 5890]: https://tools.ietf.org/html/rfc5890
|
||||||
|
[RFC 5891]: https://tools.ietf.org/html/rfc5891
|
||||||
|
[RFC 7616]: https://tools.ietf.org/html/rfc7616
|
||||||
|
[RFC 8264]: https://tools.ietf.org/html/rfc8264
|
||||||
|
[RFC 5782]: https://tools.ietf.org/html/rfc5782
|
||||||
|
[RFC 2822]: https://tools.ietf.org/html/rfc2822
|
||||||
|
[RFC 2045]: https://tools.ietf.org/html/rfc2045
|
||||||
|
[RFC 2046]: https://tools.ietf.org/html/rfc2046
|
||||||
|
[RFC 2047]: https://tools.ietf.org/html/rfc2047
|
||||||
|
[RFC 2048]: https://tools.ietf.org/html/rfc2048
|
||||||
|
[RFC 2049]: https://tools.ietf.org/html/rfc2049
|
||||||
|
[RFC 6532]: https://tools.ietf.org/html/rfc6532
|
||||||
|
[RFC 3501]: https://tools.ietf.org/html/rfc3501
|
||||||
|
[RFC 2595]: https://tools.ietf.org/html/rfc2595
|
||||||
|
[RFC 7889]: https://tools.ietf.org/html/rfc7889
|
||||||
|
[RFC 3348]: https://tools.ietf.org/html/rfc3348
|
||||||
|
[RFC 6851]: https://tools.ietf.org/html/rfc6851
|
||||||
|
[RFC 6154]: https://tools.ietf.org/html/rfc6154
|
||||||
|
[RFC 5255]: https://tools.ietf.org/html/rfc5255
|
||||||
|
[RFC 4978]: https://tools.ietf.org/html/rfc4978
|
||||||
|
[RFC 3691]: https://tools.ietf.org/html/rfc3691
|
||||||
|
[RFC 2177]: https://tools.ietf.org/html/rfc2177
|
||||||
|
[RFC 7888]: https://tools.ietf.org/html/rfc7888
|
||||||
|
[RFC 4959]: https://tools.ietf.org/html/rfc4959
|
||||||
|
[RFC 2033]: https://tools.ietf.org/html/rfc2033
|
||||||
|
[RFC 5321]: https://tools.ietf.org/html/rfc5321
|
||||||
|
[RFC 6409]: https://tools.ietf.org/html/rfc6409
|
||||||
|
[RFC 1870]: https://tools.ietf.org/html/rfc1870
|
||||||
|
[RFC 2920]: https://tools.ietf.org/html/rfc2920
|
||||||
|
[RFC 2034]: https://tools.ietf.org/html/rfc2034
|
||||||
|
[RFC 3207]: https://tools.ietf.org/html/rfc3207
|
||||||
|
[RFC 4954]: https://tools.ietf.org/html/rfc4954
|
||||||
|
[RFC 6152]: https://tools.ietf.org/html/rfc6152
|
||||||
|
[RFC 6531]: https://tools.ietf.org/html/rfc6531
|
||||||
|
[RFC 6522]: https://tools.ietf.org/html/rfc6522
|
||||||
|
[RFC 3464]: https://tools.ietf.org/html/rfc3464
|
||||||
|
[RFC 6533]: https://tools.ietf.org/html/rfc6533
|
||||||
|
[RFC 4422]: https://tools.ietf.org/html/rfc4422
|
||||||
|
[RFC 4616]: https://tools.ietf.org/html/rfc4616
|
||||||
|
[RFC 6376]: https://tools.ietf.org/html/rfc6376
|
||||||
|
[RFC 7001]: https://tools.ietf.org/html/rfc7001
|
||||||
|
[RFC 7208]: https://tools.ietf.org/html/rfc7208
|
||||||
|
[RFC 7372]: https://tools.ietf.org/html/rfc7372
|
||||||
|
[RFC 7479]: https://tools.ietf.org/html/rfc7479
|
||||||
|
[RFC 8301]: https://tools.ietf.org/html/rfc8301
|
||||||
|
[RFC 8463]: https://tools.ietf.org/html/rfc8463
|
||||||
|
[RFC 8616]: https://tools.ietf.org/html/rfc8616
|
||||||
|
[RFC 4033]: https://tools.ietf.org/html/rfc4033
|
||||||
|
[RFC 6698]: https://tools.ietf.org/html/rfc6698
|
||||||
|
[RFC 7672]: https://tools.ietf.org/html/rfc7672
|
||||||
|
[RFC 8461]: https://tools.ietf.org/html/rfc8461
|
||||||
|
[RFC 3492]: https://tools.ietf.org/html/rfc3492
|
||||||
|
[RFC 3629]: https://tools.ietf.org/html/rfc3629
|
||||||
|
[RFC 5890]: https://tools.ietf.org/html/rfc5890
|
||||||
|
[RFC 5891]: https://tools.ietf.org/html/rfc5891
|
||||||
|
[RFC 7616]: https://tools.ietf.org/html/rfc7616
|
||||||
|
[RFC 8264]: https://tools.ietf.org/html/rfc8264
|
||||||
|
[RFC 5782]: https://tools.ietf.org/html/rfc5782
|
||||||
|
[RFC 2822]: https://tools.ietf.org/html/rfc2822
|
||||||
|
[RFC 2045]: https://tools.ietf.org/html/rfc2045
|
||||||
|
[RFC 2046]: https://tools.ietf.org/html/rfc2046
|
||||||
|
[RFC 2047]: https://tools.ietf.org/html/rfc2047
|
||||||
|
[RFC 2048]: https://tools.ietf.org/html/rfc2048
|
||||||
|
[RFC 2049]: https://tools.ietf.org/html/rfc2049
|
||||||
|
[RFC 6532]: https://tools.ietf.org/html/rfc6532
|
||||||
|
[RFC 3501]: https://tools.ietf.org/html/rfc3501
|
||||||
|
[RFC 2595]: https://tools.ietf.org/html/rfc2595
|
||||||
|
[RFC 7889]: https://tools.ietf.org/html/rfc7889
|
||||||
|
[RFC 3348]: https://tools.ietf.org/html/rfc3348
|
||||||
|
[RFC 6851]: https://tools.ietf.org/html/rfc6851
|
||||||
|
[RFC 6154]: https://tools.ietf.org/html/rfc6154
|
||||||
|
[RFC 5255]: https://tools.ietf.org/html/rfc5255
|
||||||
|
[RFC 4978]: https://tools.ietf.org/html/rfc4978
|
||||||
|
[RFC 3691]: https://tools.ietf.org/html/rfc3691
|
||||||
|
[RFC 2177]: https://tools.ietf.org/html/rfc2177
|
||||||
|
[RFC 7888]: https://tools.ietf.org/html/rfc7888
|
||||||
|
[RFC 4959]: https://tools.ietf.org/html/rfc4959
|
||||||
|
[RFC 2033]: https://tools.ietf.org/html/rfc2033
|
||||||
|
[RFC 5321]: https://tools.ietf.org/html/rfc5321
|
||||||
|
[RFC 6409]: https://tools.ietf.org/html/rfc6409
|
||||||
|
[RFC 1870]: https://tools.ietf.org/html/rfc1870
|
||||||
|
[RFC 2920]: https://tools.ietf.org/html/rfc2920
|
||||||
|
[RFC 2034]: https://tools.ietf.org/html/rfc2034
|
||||||
|
[RFC 3207]: https://tools.ietf.org/html/rfc3207
|
||||||
|
[RFC 4954]: https://tools.ietf.org/html/rfc4954
|
||||||
|
[RFC 6152]: https://tools.ietf.org/html/rfc6152
|
||||||
|
[RFC 6531]: https://tools.ietf.org/html/rfc6531
|
||||||
|
[RFC 6522]: https://tools.ietf.org/html/rfc6522
|
||||||
|
[RFC 3464]: https://tools.ietf.org/html/rfc3464
|
||||||
|
[RFC 6533]: https://tools.ietf.org/html/rfc6533
|
||||||
|
[RFC 4422]: https://tools.ietf.org/html/rfc4422
|
||||||
|
[RFC 4616]: https://tools.ietf.org/html/rfc4616
|
||||||
|
[RFC 6376]: https://tools.ietf.org/html/rfc6376
|
||||||
|
[RFC 8301]: https://tools.ietf.org/html/rfc8301
|
||||||
|
[RFC 8463]: https://tools.ietf.org/html/rfc8463
|
||||||
|
[RFC 7208]: https://tools.ietf.org/html/rfc7208
|
||||||
|
[RFC 7372]: https://tools.ietf.org/html/rfc7372
|
||||||
|
[RFC 7479]: https://tools.ietf.org/html/rfc7479
|
||||||
|
[RFC 8616]: https://tools.ietf.org/html/rfc8616
|
||||||
|
[RFC 4033]: https://tools.ietf.org/html/rfc4033
|
||||||
|
[RFC 6698]: https://tools.ietf.org/html/rfc6698
|
||||||
|
[RFC 7672]: https://tools.ietf.org/html/rfc7672
|
||||||
|
[RFC 8461]: https://tools.ietf.org/html/rfc8461
|
||||||
|
[RFC 3492]: https://tools.ietf.org/html/rfc3492
|
||||||
|
[RFC 3629]: https://tools.ietf.org/html/rfc3629
|
||||||
|
[RFC 5890]: https://tools.ietf.org/html/rfc5890
|
||||||
|
[RFC 5891]: https://tools.ietf.org/html/rfc5891
|
||||||
|
[RFC 7616]: https://tools.ietf.org/html/rfc7616
|
||||||
|
[RFC 8264]: https://tools.ietf.org/html/rfc8264
|
||||||
|
[RFC 5782]: https://tools.ietf.org/html/rfc5782
|
||||||
|
|
||||||
|
[Unicode 11.0.0]: https://www.unicode.org/versions/components-11.0.0.html
|
||||||
|
[UAX #15]: https://unicode.org/reports/tr15/
|
49
docs/internals/sqlite.md
Normal file
49
docs/internals/sqlite.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# maddy & SQLite
|
||||||
|
|
||||||
|
SQLite is a perfect choice for small deployments because no additional
|
||||||
|
configuration is required to get started. It is recommended for cases when you
|
||||||
|
have less than 10 mail accounts.
|
||||||
|
|
||||||
|
**Note: SQLite requires DB-wide locking for writing, it means that multiple
|
||||||
|
messages can't be accepted in parallel. This is not the case for server-based
|
||||||
|
RDBMS where maddy can accept multiple messages in parallel even for a single
|
||||||
|
mailbox.**
|
||||||
|
|
||||||
|
## WAL mode
|
||||||
|
|
||||||
|
maddy forces WAL journal mode for SQLite. This makes things reasonably fast and
|
||||||
|
reduces locking contention which may be important for a typical mail server.
|
||||||
|
|
||||||
|
maddy uses increased WAL autocheckpoint interval. This means that while
|
||||||
|
maintaining a high write throughput, maddy will have to stop for a bit (0.5-1
|
||||||
|
second) every time 78 MiB is written to the DB (with default configuration it
|
||||||
|
is 15 MiB).
|
||||||
|
|
||||||
|
Note that when moving the database around you need to move WAL journal (`-wal`)
|
||||||
|
and shared memory (`-shm`) files as well, otherwise some changes to the DB will
|
||||||
|
be lost.
|
||||||
|
|
||||||
|
## Query planner statistics
|
||||||
|
|
||||||
|
maddy updates query planner statistics on shutdown and every 5 hours. It
|
||||||
|
provides query planner with information to access the database in more
|
||||||
|
efficient way because go-imap-sql schema does use a few so called "low-quality
|
||||||
|
indexes".
|
||||||
|
|
||||||
|
## Auto-vacuum
|
||||||
|
|
||||||
|
maddy turns on SQLite auto-vacuum feature. This means that database file size
|
||||||
|
will shrink when data is removed (compared to default when it remains unused).
|
||||||
|
|
||||||
|
## Manual vacuuming
|
||||||
|
|
||||||
|
Auto-vacuuming can lead to database fragmentation and thus reduce the read
|
||||||
|
performance. To do manual vacuum operation to repack and defragment database
|
||||||
|
file, install the SQLite3 console utility and run the following commands:
|
||||||
|
```
|
||||||
|
sqlite3 -cmd 'vacuum' database_file_path_here.db
|
||||||
|
sqlite3 -cmd 'pragma wal_checkpoint(truncate)' database_file_path_here.db
|
||||||
|
```
|
||||||
|
|
||||||
|
It will take some time to complete, you can close the utility when the
|
||||||
|
`sqlite>` prompt appears.
|
96
docs/internals/unicode.md
Normal file
96
docs/internals/unicode.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Unicode support
|
||||||
|
|
||||||
|
maddy has the first-class Unicode support in all components (modules). You do
|
||||||
|
not have to take any actions to make it work with internationalized domains,
|
||||||
|
mailbox names or non-ASCII message headers.
|
||||||
|
|
||||||
|
Internally, all text fields in maddy are represented in UTF-8 and handled using
|
||||||
|
Unicode-aware operations for comparisons, case-folding and so on.
|
||||||
|
|
||||||
|
## Non-ASCII data in message headers and bodies
|
||||||
|
|
||||||
|
maddy SMTP implementation does not care about encodings used in MIME headers or
|
||||||
|
in `Content-Type text/*` charset field.
|
||||||
|
|
||||||
|
However, local IMAP storage implementation needs to perform certain operations
|
||||||
|
on header contents. This is mostly about SEARCH functionality. For IMAP search
|
||||||
|
to work correctly, the message body and headers should use one of the following
|
||||||
|
encodings:
|
||||||
|
|
||||||
|
- ASCII
|
||||||
|
- UTF-8
|
||||||
|
- ISO-8859-1, 2, 3, 4, 9, 10, 13, 14, 15 or 16
|
||||||
|
- Windows-1250, 1251 or 1252 (aka Code Page 1250 and so on)
|
||||||
|
- KOI8-R
|
||||||
|
- ~~HZGB2312~~, GB18030
|
||||||
|
- GBK (aka Code Page 936)
|
||||||
|
- Shift JIS (aka Code Page 932 or Windows-31J)
|
||||||
|
- Big-5 (aka Code Page 950)
|
||||||
|
- EUC-JP
|
||||||
|
- ISO-2022-JP
|
||||||
|
|
||||||
|
_Support for HZGB2312 is currently disabled due to bugs with security
|
||||||
|
implications._
|
||||||
|
|
||||||
|
If mailbox includes a message with any encoding not listed here, it will not
|
||||||
|
be returned in search results for any request.
|
||||||
|
|
||||||
|
Behavior regarding handling of non-Unicode encodings is not considered stable
|
||||||
|
and may change between versions (including removal of supported encodings). If
|
||||||
|
you need your stuff to work correctly - start using UTF-8.
|
||||||
|
|
||||||
|
## Configuration files
|
||||||
|
|
||||||
|
maddy configuration files are assumed to be encoded in UTF-8. Use of any other
|
||||||
|
encoding will break stuff, do not do it.
|
||||||
|
|
||||||
|
Domain names (e.g. in hostname directive or pipeline rules) can be represented
|
||||||
|
using the ACE form (aka Punycode). They will be converted to the Unicode form
|
||||||
|
internally.
|
||||||
|
|
||||||
|
## Local credentials
|
||||||
|
|
||||||
|
'sql' storage backend and authentication provider enforce a number of additional
|
||||||
|
constraints on used account names.
|
||||||
|
|
||||||
|
PRECIS UsernameCaseMapped profile is enforced for local email addresses.
|
||||||
|
It limits the use of control and Bidi characters to make sure the used value
|
||||||
|
can be represented consistently in a variety of contexts. On top of that, the
|
||||||
|
address is case-folded and normalized to the NFC form for consistent internal
|
||||||
|
handling.
|
||||||
|
|
||||||
|
PRECIS OpaqueString profile is enforced for passwords. Less strict rules are
|
||||||
|
applied here. Runs of Unicode whitespace characters are replaced with a single
|
||||||
|
ASCII space. NFC normalization is applied afterwards. If the resulting string
|
||||||
|
is empty - the password is not accepted.
|
||||||
|
|
||||||
|
Both profiles are defined in RFC 8265, consult it for details.
|
||||||
|
|
||||||
|
## Protocol support
|
||||||
|
|
||||||
|
### SMTPUTF8 extension
|
||||||
|
|
||||||
|
maddy SMTP implementation includes support for the SMTPUTF8 extension as
|
||||||
|
defined in RFC 6531.
|
||||||
|
|
||||||
|
This means maddy can handle internationalized mailbox and domain names in MAIL
|
||||||
|
FROM, RCPT TO commands both for outbound and inbound delivery.
|
||||||
|
|
||||||
|
maddy will not accept messages with non-ASCII envelope addresses unless
|
||||||
|
SMTPUTF8 support is requested. If a message with SMTPUTF8 flag set is forwarded
|
||||||
|
to a server without SMTPUTF8 support, delivery will fail unless it is possible
|
||||||
|
to represent envelope addresses in the ASCII form (only domains use Unicode and
|
||||||
|
they can be converted to Punycode). Contents of message body (and header) are
|
||||||
|
not considered and always accepted and sent as-is, no automatic downgrading or
|
||||||
|
reencoding is done.
|
||||||
|
|
||||||
|
### IMAP UTF8, I18NLEVEL extensions
|
||||||
|
|
||||||
|
Currently, maddy does not include support for UTF8 and I18NLEVEL IMAP
|
||||||
|
extensions. However, it is not a problem that can prevent it from correctly
|
||||||
|
handling UTF-8 messages (or even messages in other non-ASCII encodings
|
||||||
|
mentioned above).
|
||||||
|
|
||||||
|
Clients that want to implement proper handling for Unicode strings may assume
|
||||||
|
maddy does not handle them properly in e.g. SEARCH commands and so such clients
|
||||||
|
may download messages and process them locally.
|
1
docs/man/.gitignore
vendored
Normal file
1
docs/man/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
_generated_*.md
|
16
docs/man/README.md
Normal file
16
docs/man/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
maddy manual pages
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
The reference documentation is maintained in the scdoc format and is compiled
|
||||||
|
into a set of Unix man pages viewable using the standard `man` utility.
|
||||||
|
|
||||||
|
See https://git.sr.ht/~sircmpwn/scdoc for information about the tool used to
|
||||||
|
build pages.
|
||||||
|
It can be used as follows:
|
||||||
|
```
|
||||||
|
scdoc < maddy-filters.5.scd > maddy-filters.5
|
||||||
|
man ./maddy-filters.5
|
||||||
|
```
|
||||||
|
|
||||||
|
build.sh script in the repo root compiles and installs man pages if the scdoc
|
||||||
|
utility is installed in the system.
|
41
docs/man/maddy.1.scd
Normal file
41
docs/man/maddy.1.scd
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
maddy(1) "maddy mail server" "maddy reference documentation"
|
||||||
|
|
||||||
|
; TITLE Command line arguments
|
||||||
|
|
||||||
|
# Name
|
||||||
|
|
||||||
|
maddy - Composable all-in-one mail server.
|
||||||
|
|
||||||
|
# Synopsis
|
||||||
|
|
||||||
|
*maddy* [options...]
|
||||||
|
|
||||||
|
# Description
|
||||||
|
|
||||||
|
Maddy is Mail Transfer agent (MTA), Mail Delivery Agent (MDA), Mail Submission
|
||||||
|
Agent (MSA), IMAP server and a set of other essential protocols/schemes
|
||||||
|
necessary to run secure email server implemented in one executable.
|
||||||
|
|
||||||
|
# Command line arguments
|
||||||
|
|
||||||
|
*-h, -help*
|
||||||
|
Show help message and exit.
|
||||||
|
|
||||||
|
*-config* _path_
|
||||||
|
Path to the configuration file. Default is /etc/maddy/maddy.conf.
|
||||||
|
|
||||||
|
*-libexec* _path_
|
||||||
|
Path to the libexec directory. Helper executables will be searched here.
|
||||||
|
Default is /usr/lib/maddy.
|
||||||
|
|
||||||
|
*-log* _targets..._
|
||||||
|
Comma-separated list of logging targets. Valid values are the same as the
|
||||||
|
'log' config directive. Affects logging before configuration parsing
|
||||||
|
completes and after it, if the different value is not specified in the
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
*-debug*
|
||||||
|
Enable debug log. You want to use it when reporting bugs.
|
||||||
|
|
||||||
|
*-v*
|
||||||
|
Print version & build metadata.
|
57
docs/man/prepare_md.py
Normal file
57
docs/man/prepare_md.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script does all necessary pre-processing to convert scdoc format into
|
||||||
|
Markdown.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
prepare_md.py < in > out
|
||||||
|
prepare_md.py file1 file2 file3
|
||||||
|
Converts into _generated_file1.md, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
anchor_escape = str.maketrans(r' #()./\+-_', '__________')
|
||||||
|
|
||||||
|
def prepare(r, w):
|
||||||
|
new_lines = list()
|
||||||
|
title = str()
|
||||||
|
previous_h1_anchor = ''
|
||||||
|
|
||||||
|
inside_literal = False
|
||||||
|
|
||||||
|
for line in r:
|
||||||
|
if not inside_literal:
|
||||||
|
if line.startswith('; TITLE ') and title == '':
|
||||||
|
title = line[8:]
|
||||||
|
if line[0] == ';':
|
||||||
|
continue
|
||||||
|
# turn *page*(1) into [**page(1)**](../_generated_page.1)
|
||||||
|
line = re.sub(r'\*(.+?)\*\(([0-9])\)', r'[*\1(\2)*](../_generated_\1.\2)', line)
|
||||||
|
# *aaa* => **aaa**
|
||||||
|
line = re.sub(r'\*(.+?)\*', r'**\1**', line)
|
||||||
|
# remove ++ from line endings
|
||||||
|
line = re.sub(r'\+\+$', '<br>', line)
|
||||||
|
# turn whatever looks like a link into one
|
||||||
|
line = re.sub(r'(https://[^ \)\(\\]+[a-z0-9_\-])', r'[\1](\1)', line)
|
||||||
|
# escape underscores inside words
|
||||||
|
line = re.sub(r'([^ ])_([^ ])', r'\1\\_\2', line)
|
||||||
|
|
||||||
|
if line.startswith('```'):
|
||||||
|
inside_literal = not inside_literal
|
||||||
|
|
||||||
|
new_lines.append(line)
|
||||||
|
|
||||||
|
if title != '':
|
||||||
|
print('#', title, file=w)
|
||||||
|
|
||||||
|
print(''.join(new_lines[1:]), file=w)
|
||||||
|
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
prepare(sys.stdin, sys.stdout)
|
||||||
|
else:
|
||||||
|
for f in sys.argv[1:]:
|
||||||
|
new_name = '_generated_' + f[:-4] + '.md'
|
||||||
|
prepare(open(f, 'r'), open(new_name, 'w'))
|
157
docs/multiple-domains.md
Normal file
157
docs/multiple-domains.md
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
# Multiple domains configuration
|
||||||
|
|
||||||
|
By default, maddy uses email addresses as account identifiers for both
|
||||||
|
authentication and storage purposes. Therefore, account named `user@example.org`
|
||||||
|
is completely independent from `user@example.com`. They must be created
|
||||||
|
separately, may have different credentials and have separate IMAP mailboxes.
|
||||||
|
|
||||||
|
This makes it extremely easy to setup maddy to manage multiple otherwise
|
||||||
|
independent domains.
|
||||||
|
|
||||||
|
Default configuration file contains two macros - `$(primary_domain)` and
|
||||||
|
`$(local_domains)`. They are used to used in several places thorough the
|
||||||
|
file to configure message routing, security checks, etc.
|
||||||
|
|
||||||
|
In general, you should just add all domains you want maddy to manage to
|
||||||
|
`$(local_domains)`, like this:
|
||||||
|
```
|
||||||
|
$(primary_domain) = example.org
|
||||||
|
$(local_domains) = $(primary_domain) example.com
|
||||||
|
```
|
||||||
|
Note that you need to pick one domain as a "primary" for use in
|
||||||
|
auto-generated messages.
|
||||||
|
|
||||||
|
With that done, you can create accounts using both domains in the name, send
|
||||||
|
and receive messages and so on. Do not forget to configure corresponding SPF,
|
||||||
|
DMARC and MTA-STS records as was recommended in
|
||||||
|
the [introduction tutorial](tutorials/setting-up.md).
|
||||||
|
|
||||||
|
Also note that you do not really need a separate TLS certificate for each
|
||||||
|
managed domain. You can have one hostname e.g. mail.example.org set as an MX
|
||||||
|
record for multiple domains.
|
||||||
|
|
||||||
|
**If you want multiple domains to share username namespace**, you should change
|
||||||
|
several more options.
|
||||||
|
|
||||||
|
You can make "user@example.org" and "user@example.com" users share the same
|
||||||
|
credentials of user "user" but have different IMAP mailboxes ("user@example.org"
|
||||||
|
and "user@example.com" correspondingly). For that, it is enough to set `auth_map`
|
||||||
|
globally to use `email_localpart` table:
|
||||||
|
```
|
||||||
|
auth_map email_localpart
|
||||||
|
```
|
||||||
|
This way, when user logs in as "user@example.org", "user" will be passed
|
||||||
|
to the authentication provider, but "user@example.org" will be passed to the
|
||||||
|
storage backend. You should create accounts like this:
|
||||||
|
```
|
||||||
|
maddy creds create user
|
||||||
|
maddy imap-acct create user@example.org
|
||||||
|
maddy imap-acct create user@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**If you want accounts to also share the same IMAP storage of account named
|
||||||
|
"user"**, you can set `storage_map` in IMAP endpoint and `delivery_map` in
|
||||||
|
storage backend to use `email_locapart`:
|
||||||
|
```
|
||||||
|
storage.imapsql local_mailboxes {
|
||||||
|
...
|
||||||
|
delivery_map email_localpart # deliver "user@*" to "user"
|
||||||
|
}
|
||||||
|
imap tls://0.0.0.0:993 {
|
||||||
|
...
|
||||||
|
storage &local_mailboxes
|
||||||
|
...
|
||||||
|
storage_map email_localpart # "user@*" accesses "user" mailbox
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You also might want to make it possible to log in without
|
||||||
|
specifying a domain at all. In this case, use `email_localpart_optional` for
|
||||||
|
both `auth_map` and `storage_map`.
|
||||||
|
|
||||||
|
You also need to make `authorize_sender` check (used in `submission` endpoint)
|
||||||
|
accept non-email usernames:
|
||||||
|
```
|
||||||
|
authorize_sender {
|
||||||
|
...
|
||||||
|
user_to_email chain {
|
||||||
|
step email_localpart_optional # remove domain from username if present
|
||||||
|
step email_with_domain $(local_domains) # expand username with all allowed domains
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Your options:
|
||||||
|
|
||||||
|
**"user@example.org" and "user@example.com" have distinct credentials and
|
||||||
|
distinct mailboxes.**
|
||||||
|
|
||||||
|
```
|
||||||
|
$(primary_domain) = example.org
|
||||||
|
$(local_domains) = example.org example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Create accounts as:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
maddy creds create user@example.org
|
||||||
|
maddy imap-acct create user@example.org
|
||||||
|
maddy creds create user@example.com
|
||||||
|
maddy imap-acct create user@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**"user@example.org" and "user@example.com" have same credentials but
|
||||||
|
distinct mailboxes.**
|
||||||
|
|
||||||
|
```
|
||||||
|
$(primary_domain) = example.org
|
||||||
|
$(local_domains) = example.org example.com
|
||||||
|
auth_map email_localpart
|
||||||
|
```
|
||||||
|
|
||||||
|
Create accounts as:
|
||||||
|
```shell
|
||||||
|
maddy creds create user
|
||||||
|
maddy imap-acct create user@example.org
|
||||||
|
maddy imap-acct create user@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**"user@example.org", "user@example.com", "user" have same credentials and same
|
||||||
|
mailboxes.**
|
||||||
|
|
||||||
|
```
|
||||||
|
$(primary_domain) = example.org
|
||||||
|
$(local_domains) = example.org example.com
|
||||||
|
auth_map email_localpart_optional # authenticating as "user@*" checks credentials for "user"
|
||||||
|
|
||||||
|
storage.imapsql local_mailboxes {
|
||||||
|
...
|
||||||
|
delivery_map email_localpart_optional # deliver "user@*" to "user" mailbox
|
||||||
|
}
|
||||||
|
|
||||||
|
imap tls://0.0.0.0:993 {
|
||||||
|
...
|
||||||
|
storage_map email_localpart_optional # authenticating as "user@*" accesses "user" mailboxes
|
||||||
|
}
|
||||||
|
|
||||||
|
submission tls://0.0.0.0:465 {
|
||||||
|
check {
|
||||||
|
authorize_sender {
|
||||||
|
...
|
||||||
|
user_to_email chain {
|
||||||
|
step email_localpart_optional # remove domain from username if present
|
||||||
|
step email_with_domain $(local_domains) # expand username with all allowed domains
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create accounts as:
|
||||||
|
```shell
|
||||||
|
maddy creds create user
|
||||||
|
maddy imap-acct create user
|
||||||
|
```
|
26
docs/reference/auth/dovecot_sasl.md
Normal file
26
docs/reference/auth/dovecot_sasl.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Dovecot SASL
|
||||||
|
|
||||||
|
The 'auth.dovecot_sasl' module implements the client side of the Dovecot
|
||||||
|
authentication protocol, allowing maddy to use it as a credentials source.
|
||||||
|
|
||||||
|
Currently SASL mechanisms support is limited to mechanisms supported by maddy
|
||||||
|
so you cannot get e.g. SCRAM-MD5 this way.
|
||||||
|
|
||||||
|
```
|
||||||
|
auth.dovecot_sasl {
|
||||||
|
endpoint unix://socket_path
|
||||||
|
}
|
||||||
|
|
||||||
|
dovecot_sasl unix://socket_path
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### endpoint _schema://address_
|
||||||
|
Default: not set
|
||||||
|
|
||||||
|
Set the address to use to contact Dovecot SASL server in the standard endpoint
|
||||||
|
format.
|
||||||
|
|
||||||
|
`tcp://10.0.0.1:2222` for TCP, `unix:///var/lib/dovecot/auth.sock` for Unix
|
||||||
|
domain sockets.
|
52
docs/reference/auth/external.md
Normal file
52
docs/reference/auth/external.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# System command
|
||||||
|
|
||||||
|
auth.external module for authentication using external helper binary. It looks for binary
|
||||||
|
named `maddy-auth-helper` in $PATH and libexecdir and uses it for authentication
|
||||||
|
using username/password pair.
|
||||||
|
|
||||||
|
The protocol is very simple:
|
||||||
|
Program is launched for each authentication. Username and password are written
|
||||||
|
to stdin, adding \n to the end. If binary exits with 0 status code -
|
||||||
|
authentication is considered successful. If the status code is 1 -
|
||||||
|
authentication is failed. If the status code is 2 - another unrelated error has
|
||||||
|
happened. Additional information should be written to stderr.
|
||||||
|
|
||||||
|
```
|
||||||
|
auth.external {
|
||||||
|
helper /usr/bin/ldap-helper
|
||||||
|
perdomain no
|
||||||
|
domains example.org
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### helper _file_path_
|
||||||
|
|
||||||
|
**Required.** <br>
|
||||||
|
Location of the helper binary.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### perdomain _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Don't remove domain part of username when authenticating and require it to be
|
||||||
|
present. Can be used if you want user@domain1 and user@domain2 to be different
|
||||||
|
accounts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### domains _domains..._
|
||||||
|
Default: not specified
|
||||||
|
|
||||||
|
Domains that should be allowed in username during authentication.
|
||||||
|
|
||||||
|
For example, if 'domains' is set to "domain1 domain2", then
|
||||||
|
username, username@domain1 and username@domain2 will be accepted as valid login
|
||||||
|
name in addition to just username.
|
||||||
|
|
||||||
|
If used without 'perdomain', domain part will be removed from login before
|
||||||
|
check with underlying auth. mechanism. If 'perdomain' is set, then
|
||||||
|
domains must be also set and domain part **will not** be removed before check.
|
||||||
|
|
130
docs/reference/auth/ldap.md
Normal file
130
docs/reference/auth/ldap.md
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# LDAP BindDN
|
||||||
|
|
||||||
|
maddy supports authentication via LDAP using DN binding. Passwords are verified
|
||||||
|
by the LDAP server.
|
||||||
|
|
||||||
|
maddy needs to know the DN to use for binding. It can be obtained either by
|
||||||
|
directory search or template .
|
||||||
|
|
||||||
|
Note that storage backends conventionally use email addresses, if you use
|
||||||
|
non-email identifiers as usernames then you should map them onto
|
||||||
|
emails on delivery by using `auth_map` (see documentation page for used storage backend).
|
||||||
|
|
||||||
|
auth.ldap also can be a used as a table module. This way you can check
|
||||||
|
whether the account exists. It works only if DN template is not used.
|
||||||
|
|
||||||
|
```
|
||||||
|
auth.ldap {
|
||||||
|
urls ldap://maddy.test:389
|
||||||
|
|
||||||
|
# Specify initial bind credentials. Not required ('bind off')
|
||||||
|
# if DN template is used.
|
||||||
|
bind plain "cn=maddy,ou=people,dc=maddy,dc=test" "123456"
|
||||||
|
|
||||||
|
# Specify DN template to skip lookup.
|
||||||
|
dn_template "cn={username},ou=people,dc=maddy,dc=test"
|
||||||
|
|
||||||
|
# Specify base_dn and filter to lookup DN.
|
||||||
|
base_dn "ou=people,dc=maddy,dc=test"
|
||||||
|
filter "(&(objectClass=posixAccount)(uid={username}))"
|
||||||
|
|
||||||
|
tls_client { ... }
|
||||||
|
starttls off
|
||||||
|
debug off
|
||||||
|
connect_timeout 1m
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```
|
||||||
|
auth.ldap ldap://maddy.test.389 {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### urls _servers..._
|
||||||
|
|
||||||
|
**Required.**
|
||||||
|
|
||||||
|
URLs of the directory servers to use. First available server
|
||||||
|
is used - no load-balancing is done.
|
||||||
|
|
||||||
|
URLs should use `ldap://`, `ldaps://`, `ldapi://` schemes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### bind `off` | `unauth` | `external` | `plain` _username_ _password_
|
||||||
|
|
||||||
|
Default: `off`
|
||||||
|
|
||||||
|
Credentials to use for initial binding. Required if DN lookup is used.
|
||||||
|
|
||||||
|
`unauth` performs unauthenticated bind. `external` performs external binding
|
||||||
|
which is useful for Unix socket connections (`ldapi://`) or TLS client certificate
|
||||||
|
authentication (cert. is set using tls_client directive). `plain` performs a
|
||||||
|
simple bind using provided credentials.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### dn_template _template_
|
||||||
|
|
||||||
|
DN template to use for binding. `{username}` is replaced with the
|
||||||
|
username specified by the user.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### base_dn _dn_
|
||||||
|
|
||||||
|
Base DN to use for lookup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### filter _str_
|
||||||
|
|
||||||
|
DN lookup filter. `{username}` is replaced with the username specified
|
||||||
|
by the user.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
(&(objectClass=posixAccount)(uid={username}))
|
||||||
|
```
|
||||||
|
|
||||||
|
Example (using ActiveDirectory):
|
||||||
|
|
||||||
|
```
|
||||||
|
(&(objectCategory=Person)(memberOf=CN=user-group,OU=example,DC=example,DC=org)(sAMAccountName={username})(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
(&(objectClass=Person)(mail={username}))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### starttls _bool_
|
||||||
|
Default: `off`
|
||||||
|
|
||||||
|
Whether to upgrade connection to TLS using STARTTLS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### tls_client { ... }
|
||||||
|
|
||||||
|
Advanced TLS client configuration. See [TLS configuration / Client](/reference/tls/#client) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### connect_timeout _duration_
|
||||||
|
Default: `1m`
|
||||||
|
|
||||||
|
Timeout for initial connection to the directory server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### request_timeout _duration_
|
||||||
|
Default: `1m`
|
||||||
|
|
||||||
|
Timeout for each request (binding, lookup).
|
48
docs/reference/auth/netauth.md
Normal file
48
docs/reference/auth/netauth.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Native NetAuth
|
||||||
|
|
||||||
|
maddy supports authentication via NetAuth using direct entity
|
||||||
|
authentication checks. Passwords are verified by the NetAuth server.
|
||||||
|
|
||||||
|
maddy needs to know the Entity ID to use for authentication. It must
|
||||||
|
match the string the user provides for the Local Atom part of their
|
||||||
|
mail address.
|
||||||
|
|
||||||
|
Note that storage backends conventionally use email addresses. Since
|
||||||
|
NetAuth recommends *nix compatible usernames, you will need to map the
|
||||||
|
email identifiers to NetAuth Entity IDs using `auth_map` (see
|
||||||
|
documentation page for used storage backend).
|
||||||
|
|
||||||
|
auth.netauth also can be used as a table module. This way you can
|
||||||
|
check whether the account exists.
|
||||||
|
|
||||||
|
Note that the configuration fragment provided below is very sparse.
|
||||||
|
This is because NetAuth expects to read most of its common
|
||||||
|
configuration values from the system NetAuth config file located at
|
||||||
|
`/etc/netauth/config.toml`.
|
||||||
|
|
||||||
|
```
|
||||||
|
auth.netauth {
|
||||||
|
require_group "maddy-users"
|
||||||
|
debug off
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
auth.netauth {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### require_group _group_
|
||||||
|
|
||||||
|
Optional.
|
||||||
|
|
||||||
|
Group that entities must possess to be able to use maddy services.
|
||||||
|
This can be used to provide email to just a subset of the entities
|
||||||
|
present in NetAuth.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### debug `on` | `off`
|
||||||
|
|
||||||
|
Default: `off`
|
48
docs/reference/auth/pam.md
Normal file
48
docs/reference/auth/pam.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# PAM
|
||||||
|
|
||||||
|
auth.pam module implements authentication using libpam. Alternatively it can be configured to
|
||||||
|
use helper binary like auth.external module does.
|
||||||
|
|
||||||
|
maddy should be built with libpam build tag to use this module without
|
||||||
|
'use_helper' directive.
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -tags 'libpam' ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
auth.pam {
|
||||||
|
debug no
|
||||||
|
use_helper no
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Enable verbose logging for all modules. You don't need that unless you are
|
||||||
|
reporting a bug.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### use_helper _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Use `LibexecDirectory/maddy-pam-helper` instead of directly calling libpam.
|
||||||
|
You need to use that if:
|
||||||
|
|
||||||
|
1. maddy is not compiled with libpam, but `maddy-pam-helper` is built separately.
|
||||||
|
2. maddy is running as an unprivileged user and used PAM configuration requires additional privileges (e.g. when using system accounts).
|
||||||
|
|
||||||
|
For 2, you need to make `maddy-pam-helper` binary setuid, see
|
||||||
|
README.md in source tree for details.
|
||||||
|
|
||||||
|
TL;DR (assuming you have the maddy group):
|
||||||
|
|
||||||
|
```
|
||||||
|
chown root:maddy /usr/lib/maddy/maddy-pam-helper
|
||||||
|
chmod u+xs,g+x,o-x /usr/lib/maddy/maddy-pam-helper
|
||||||
|
```
|
||||||
|
|
44
docs/reference/auth/pass_table.md
Normal file
44
docs/reference/auth/pass_table.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Password table
|
||||||
|
|
||||||
|
auth.pass_table module implements username:password authentication by looking up the
|
||||||
|
password hash using a table module (maddy-tables(5)). It can be used
|
||||||
|
to load user credentials from text file (via table.file module) or SQL query
|
||||||
|
(via table.sql_table module).
|
||||||
|
|
||||||
|
|
||||||
|
Definition:
|
||||||
|
```
|
||||||
|
auth.pass_table [block name] {
|
||||||
|
table <table config>
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Shortened variant for inline use:
|
||||||
|
```
|
||||||
|
pass_table <table> [table arguments] {
|
||||||
|
[additional table config]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example, read username:password pair from the text file:
|
||||||
|
```
|
||||||
|
smtp tcp://0.0.0.0:587 {
|
||||||
|
auth pass_table file /etc/maddy/smtp_passwd
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Password hashes
|
||||||
|
|
||||||
|
pass_table expects the used table to contain certain structured values with
|
||||||
|
hash algorithm name, salt and other necessary parameters.
|
||||||
|
|
||||||
|
You should use `maddy hash` command to generate suitable values.
|
||||||
|
See `maddy hash --help` for details.
|
||||||
|
|
||||||
|
## maddy creds
|
||||||
|
|
||||||
|
If the underlying table is a "mutable" table (see maddy-tables(5)) then
|
||||||
|
the `maddy creds` command can be used to modify the underlying tables
|
||||||
|
via pass_table module. It will act on a "local credentials store" and will write
|
||||||
|
appropriate hash values to the table.
|
45
docs/reference/auth/plain_separate.md
Normal file
45
docs/reference/auth/plain_separate.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Separate username and password lookup
|
||||||
|
|
||||||
|
auth.plain_separate module implements authentication using username:password pairs but can
|
||||||
|
use zero or more "table modules" (maddy-tables(5)) and one or more
|
||||||
|
authentication providers to verify credentials.
|
||||||
|
|
||||||
|
```
|
||||||
|
auth.plain_separate {
|
||||||
|
user ...
|
||||||
|
user ...
|
||||||
|
...
|
||||||
|
pass ...
|
||||||
|
pass ...
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
How it works:
|
||||||
|
- Initial username input is normalized using PRECIS UsernameCaseMapped profile.
|
||||||
|
- Each table specified with the 'user' directive looked up using normalized
|
||||||
|
username. If match is not found in any table, authentication fails.
|
||||||
|
- Each authentication provider specified with the 'pass' directive is tried.
|
||||||
|
If authentication with all providers fails - an error is returned.
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### user _table-module_
|
||||||
|
|
||||||
|
Configuration block for any module from maddy-tables(5) can be used here.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
user file /etc/maddy/allowed_users
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### pass _auth-provider_
|
||||||
|
|
||||||
|
Configuration block for any auth. provider module can be used here, even
|
||||||
|
'plain_split' itself.
|
||||||
|
|
||||||
|
The used auth. provider must provide username:password pair-based
|
||||||
|
authentication.
|
40
docs/reference/auth/shadow.md
Normal file
40
docs/reference/auth/shadow.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# /etc/shadow
|
||||||
|
|
||||||
|
auth.shadow module implements authentication by reading /etc/shadow. Alternatively it can be
|
||||||
|
configured to use helper binary like auth.external does.
|
||||||
|
|
||||||
|
```
|
||||||
|
auth.shadow {
|
||||||
|
debug no
|
||||||
|
use_helper no
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Enable verbose logging for all modules. You don't need that unless you are
|
||||||
|
reporting a bug.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### use_helper _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Use `LibexecDirectory/maddy-shadow-helper` instead of directly reading `/etc/shadow`.
|
||||||
|
You need to use that if maddy is running as an unprivileged user
|
||||||
|
privileges (e.g. when using system accounts).
|
||||||
|
|
||||||
|
You need to make `maddy-shadow-helper` binary setuid, see
|
||||||
|
cmd/maddy-shadow-helper/README.md in source tree for details.
|
||||||
|
|
||||||
|
TL;DR (assuming you have maddy group):
|
||||||
|
|
||||||
|
```
|
||||||
|
chown root:maddy /usr/lib/maddy/maddy-shadow-helper
|
||||||
|
chmod u+xs,g+x,o-x /usr/lib/maddy/maddy-shadow-helper
|
||||||
|
```
|
||||||
|
|
23
docs/reference/blob/fs.md
Normal file
23
docs/reference/blob/fs.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Filesystem
|
||||||
|
|
||||||
|
This module stores message bodies in a file system directory.
|
||||||
|
|
||||||
|
```
|
||||||
|
storage.blob.fs {
|
||||||
|
root <directory>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
storage.blob.fs <directory>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### root _path_
|
||||||
|
Default: not set
|
||||||
|
|
||||||
|
Path to the FS directory. Must be readable and writable by the server process.
|
||||||
|
If it does not exist - it will be created (parent directory should be writable
|
||||||
|
for this). Relative paths are interpreted relatively to server state directory.
|
||||||
|
|
98
docs/reference/blob/s3.md
Normal file
98
docs/reference/blob/s3.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# Amazon S3
|
||||||
|
|
||||||
|
storage.blob.s3 module stores messages bodies in a bucket on S3-compatible storage.
|
||||||
|
|
||||||
|
```
|
||||||
|
storage.blob.s3 {
|
||||||
|
endpoint play.min.io
|
||||||
|
secure yes
|
||||||
|
access_key "Q3AM3UQ867SPQQA43P2F"
|
||||||
|
secret_key "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
|
||||||
|
bucket maddy-test
|
||||||
|
|
||||||
|
# optional
|
||||||
|
region eu-central-1
|
||||||
|
object_prefix maddy/
|
||||||
|
creds access_key
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
storage.imapsql local_mailboxes {
|
||||||
|
...
|
||||||
|
msg_store s3 {
|
||||||
|
endpoint s3.amazonaws.com
|
||||||
|
access_key "..."
|
||||||
|
secret_key "..."
|
||||||
|
bucket maddy-messages
|
||||||
|
region us-west-2
|
||||||
|
creds access_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### endpoint _address:port_
|
||||||
|
|
||||||
|
**Required**.
|
||||||
|
|
||||||
|
Root S3 endpoint. e.g. `s3.amazonaws.com`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### secure _boolean_
|
||||||
|
Default: `yes`
|
||||||
|
|
||||||
|
Whether TLS should be used.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### access_key _string_<br>secret_key _string_
|
||||||
|
|
||||||
|
**Required**.
|
||||||
|
|
||||||
|
Static S3 credentials.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### bucket _name_
|
||||||
|
|
||||||
|
**Required**.
|
||||||
|
|
||||||
|
S3 bucket name. The bucket must exist and
|
||||||
|
be read-writable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### region _string_
|
||||||
|
Default: not set
|
||||||
|
|
||||||
|
S3 bucket location. May be called "endpoint" in some manuals.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### object_prefix _string_
|
||||||
|
Default: empty string
|
||||||
|
|
||||||
|
String to add to all keys stored by maddy.
|
||||||
|
|
||||||
|
Can be useful when S3 is used as a file system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### creds `access_key` | `file_minio` | `file_aws` | `iam`
|
||||||
|
Default: `access_key`
|
||||||
|
|
||||||
|
Credentials to use for accessing the S3 Bucket.
|
||||||
|
|
||||||
|
Credential Types:
|
||||||
|
|
||||||
|
- `access_key`: use AWS access key and secret access key
|
||||||
|
- `file_minio`: use credentials for Minio present at ~/.mc/config.json
|
||||||
|
- `file_aws`: use credentials for AWS S3 present at ~/.aws/credentials
|
||||||
|
- `iam`: use AWS IAM instance profile for credentials.
|
||||||
|
|
||||||
|
By default, access_key is used with the access key and secret access key present in the config.
|
21
docs/reference/checks/actions.md
Normal file
21
docs/reference/checks/actions.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Check actions
|
||||||
|
|
||||||
|
When a certain check module thinks the message is "bad", it takes some actions
|
||||||
|
depending on its configuration. Most checks follow the same configuration
|
||||||
|
structure and allow following actions to be taken on check failure:
|
||||||
|
|
||||||
|
- Do nothing (`action ignore`)
|
||||||
|
|
||||||
|
Useful for testing deployment of new checks. Check failures are still logged
|
||||||
|
but they have no effect on message delivery.
|
||||||
|
|
||||||
|
- Reject the message (`action reject`)
|
||||||
|
|
||||||
|
Reject the message at connection time. No bounce is generated locally.
|
||||||
|
|
||||||
|
- Quarantine the message (`action quarantine`)
|
||||||
|
|
||||||
|
Mark message as 'quarantined'. If message is then delivered to the local
|
||||||
|
storage, the storage backend can place the message in the 'Junk' mailbox.
|
||||||
|
Another thing to keep in mind that 'target.remote' module
|
||||||
|
will refuse to send quarantined messages.
|
132
docs/reference/checks/authorize_sender.md
Normal file
132
docs/reference/checks/authorize_sender.md
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# MAIL FROM and From authorization
|
||||||
|
|
||||||
|
Module check.authorize_sender verifies that envelope and header sender addresses belong
|
||||||
|
to the authenticated user. Address ownership is established via table
|
||||||
|
that maps each user account to a email address it is allowed to use.
|
||||||
|
There are some special cases, see `user_to_email` description below.
|
||||||
|
|
||||||
|
```
|
||||||
|
check.authorize_sender {
|
||||||
|
prepare_email identity
|
||||||
|
user_to_email identity
|
||||||
|
check_header yes
|
||||||
|
|
||||||
|
unauth_action reject
|
||||||
|
no_match_action reject
|
||||||
|
malformed_action reject
|
||||||
|
err_action reject
|
||||||
|
|
||||||
|
auth_normalize auto
|
||||||
|
from_normalize auto
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```
|
||||||
|
check {
|
||||||
|
authorize_sender { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### user_to_email _table_
|
||||||
|
Default: `identity`
|
||||||
|
|
||||||
|
Table that maps authorization username to the list of sender emails
|
||||||
|
the user is allowed to use.
|
||||||
|
|
||||||
|
In additional to email addresses, the table can contain domain names or
|
||||||
|
special string "\*" as a value. If the value is a domain - user
|
||||||
|
will be allowed to use any mailbox within it as a sender address.
|
||||||
|
If it is "\*" - user will be allowed to use any address.
|
||||||
|
|
||||||
|
By default, table.identity is used, meaning that username should
|
||||||
|
be equal to the sender email.
|
||||||
|
|
||||||
|
Before username is looked up via the table, normalization algorithm
|
||||||
|
defined by auth_normalize is applied to it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### prepare_email _table_
|
||||||
|
Default: `identity`
|
||||||
|
|
||||||
|
Table that is used to translate email addresses before they
|
||||||
|
are matched against user_to_email values.
|
||||||
|
|
||||||
|
Typically used to allow users to use their aliases as sender
|
||||||
|
addresses - prepare_email in this case should translate
|
||||||
|
aliases to "canonical" addresses. This is how it is
|
||||||
|
done in default configuration.
|
||||||
|
|
||||||
|
If table does not contain any mapping for the used sender
|
||||||
|
address, it will be used as is.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### check_header _boolean_
|
||||||
|
Default: `yes`
|
||||||
|
|
||||||
|
Whether to verify header sender in addition to envelope.
|
||||||
|
|
||||||
|
Either Sender or From field value should match the
|
||||||
|
authorization identity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### unauth_action _action_
|
||||||
|
Default: `reject`
|
||||||
|
|
||||||
|
What to do if the user is not authenticated at all.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### no_match_action _action_
|
||||||
|
Default: `reject`
|
||||||
|
|
||||||
|
What to do if user is not allowed to use the sender address specified.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### malformed_action _action_
|
||||||
|
Default: `reject`
|
||||||
|
|
||||||
|
What to do if From or Sender header fields contain malformed values.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### err_action _action_
|
||||||
|
Default: `reject`
|
||||||
|
|
||||||
|
What to do if error happens during prepare_email or user_to_email lookup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### auth_normalize _action_
|
||||||
|
Default: `auto`
|
||||||
|
|
||||||
|
Normalization function to apply to authorization username before
|
||||||
|
further processing.
|
||||||
|
|
||||||
|
Available options:
|
||||||
|
|
||||||
|
- `auto` `precis_casefold_email` for valid emails, `precis_casefold` otherwise.
|
||||||
|
- `precis_casefold_email` PRECIS UsernameCaseMapped profile + U-labels form for domain
|
||||||
|
- `precis_casefold` PRECIS UsernameCaseMapped profile for the entire string
|
||||||
|
- `precis_email` PRECIS UsernameCasePreserved profile + U-labels form for domain
|
||||||
|
- `precis` PRECIS UsernameCasePreserved profile for the entire string
|
||||||
|
- `casefold` Convert to lower case
|
||||||
|
- `noop` Nothing
|
||||||
|
|
||||||
|
PRECIS profiles are defined by RFC 8265. In short, they make sure
|
||||||
|
that Unicode strings that look the same will be compared as if they were
|
||||||
|
the same. CaseMapped profiles also convert strings to lower case.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### from_normalize _action_
|
||||||
|
Default: `auto`
|
||||||
|
|
||||||
|
Normalization function to apply to email addresses before
|
||||||
|
further processing.
|
||||||
|
|
||||||
|
Available options are same as for `auth_normalize`.
|
96
docs/reference/checks/command.md
Normal file
96
docs/reference/checks/command.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# System command filter
|
||||||
|
|
||||||
|
This module executes an arbitrary system command during a specified stage of
|
||||||
|
checks execution.
|
||||||
|
|
||||||
|
```
|
||||||
|
command executable_name arg0 arg1 ... {
|
||||||
|
run_on body
|
||||||
|
|
||||||
|
code 1 reject
|
||||||
|
code 2 quarantine
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
The module arguments specify the command to run. If the first argument is not
|
||||||
|
an absolute path, it is looked up in the Libexec Directory (/usr/lib/maddy on
|
||||||
|
Linux) and in $PATH (in that ordering). Note that no additional handling
|
||||||
|
of arguments is done, especially, the command is executed directly, not via the
|
||||||
|
system shell.
|
||||||
|
|
||||||
|
There is a set of special strings that are replaced with the corresponding
|
||||||
|
message-specific values:
|
||||||
|
|
||||||
|
- `{source_ip}` – IPv4/IPv6 address of the sending MTA.
|
||||||
|
- `{source_host}` – Hostname of the sending MTA, from the HELO/EHLO command.
|
||||||
|
- `{source_rdns}` – PTR record of the sending MTA IP address.
|
||||||
|
- `{msg_id}` – Internal message identifier. Unique for each delivery.
|
||||||
|
- `{auth_user}` – Client username, if authenticated using SASL PLAIN
|
||||||
|
- `{sender}` – Message sender address, as specified in the MAIL FROM SMTP command.
|
||||||
|
- `{rcpts}` – List of accepted recipient addresses, including the currently handled
|
||||||
|
one.
|
||||||
|
- `{address}` – Currently handled address. This is a recipient address if the command
|
||||||
|
is called during RCPT TO command handling (`run_on rcpt`) or a sender
|
||||||
|
address if the command is called during MAIL FROM command handling (`run_on
|
||||||
|
sender`).
|
||||||
|
|
||||||
|
If value is undefined (e.g. `{source_ip}` for a message accepted over a Unix
|
||||||
|
socket) or unavailable (the command is executed too early), the placeholder
|
||||||
|
is replaced with an empty string. Note that it can not remove the argument.
|
||||||
|
E.g. `-i {source_ip}` will not become just `-i`, it will be `-i ""`
|
||||||
|
|
||||||
|
Undefined placeholders are not replaced.
|
||||||
|
|
||||||
|
## Command stdout
|
||||||
|
|
||||||
|
The command stdout must be either empty or contain a valid RFC 5322 header.
|
||||||
|
If it contains a byte stream that does not look a valid header, the message
|
||||||
|
will be rejected with a temporary error.
|
||||||
|
|
||||||
|
The header from stdout will be **prepended** to the message header.
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### run_on `conn` | `sender` | `rcpt` | `body`
|
||||||
|
Default: `body`
|
||||||
|
|
||||||
|
When to run the command. This directive also affects the information visible
|
||||||
|
for the message.
|
||||||
|
|
||||||
|
- `conn`<br>
|
||||||
|
Run before the sender address (MAIL FROM) is handled.<br>
|
||||||
|
**Stdin**: Empty <br>
|
||||||
|
**Available placeholders**: {source_ip}, {source_host}, {msg_id}, {auth_user}.
|
||||||
|
|
||||||
|
- `sender`<br>
|
||||||
|
Run during sender address (MAIL FROM) handling.<br>
|
||||||
|
**Stdin**: Empty <br>
|
||||||
|
**Available placeholders**: conn placeholders + {sender}, {address}.
|
||||||
|
The {address} placeholder contains the MAIL FROM address.
|
||||||
|
|
||||||
|
- `rcpt`<br>
|
||||||
|
Run during recipient address (RCPT TO) handling. The command is executed
|
||||||
|
once for each RCPT TO command, even if the same recipient is specified
|
||||||
|
multiple times.<br>
|
||||||
|
**Stdin**: Empty <br>
|
||||||
|
**Available placeholders**: sender placeholders + {rcpts}.
|
||||||
|
The {address} placeholder contains the recipient address.
|
||||||
|
|
||||||
|
- `body`<br>
|
||||||
|
Run during message body handling.<br>
|
||||||
|
**Stdin**: The message header + body <br>
|
||||||
|
**Available placeholders**: all except for {address}.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### code _integer_ ignore <br>code _integer_ quarantine <br>code _integer_ reject _smtp-code_ _smtp-enhanced-code_ _smtp-message_
|
||||||
|
|
||||||
|
This directive specifies the mapping from the command exit code _integer_ to
|
||||||
|
the message pipeline action.
|
||||||
|
|
||||||
|
Two codes are defined implicitly, exit code 1 causes the message to be rejected
|
||||||
|
with a permanent error, exit code 2 causes the message to be quarantined. Both
|
||||||
|
actions can be overridden using the 'code' directive.
|
||||||
|
|
63
docs/reference/checks/dkim.md
Normal file
63
docs/reference/checks/dkim.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# DKIM
|
||||||
|
|
||||||
|
This is the check module that performs verification of the DKIM signatures
|
||||||
|
present on the incoming messages.
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
```
|
||||||
|
check.dkim {
|
||||||
|
debug no
|
||||||
|
required_fields From Subject
|
||||||
|
allow_body_subset no
|
||||||
|
no_sig_action ignore
|
||||||
|
broken_sig_action ignore
|
||||||
|
fail_open no
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
Log both successful and unsuccessful check executions instead of just
|
||||||
|
unsuccessful.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### required_fields _string..._
|
||||||
|
Default: `From Subject`
|
||||||
|
|
||||||
|
Header fields that should be included in each signature. If signature
|
||||||
|
lacks any field listed in that directive, it will be considered invalid.
|
||||||
|
|
||||||
|
Note that From is always required to be signed, even if it is not included in
|
||||||
|
this directive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### no_sig_action _action_
|
||||||
|
Default: `ignore` (recommended by RFC 6376)
|
||||||
|
|
||||||
|
Action to take when message without any signature is received.
|
||||||
|
|
||||||
|
Note that DMARC policy of the sender domain can request more strict handling of
|
||||||
|
missing DKIM signatures.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### broken_sig_action _action_
|
||||||
|
Default: `ignore` (recommended by RFC 6376)
|
||||||
|
|
||||||
|
Action to take when there are not valid signatures in a message.
|
||||||
|
|
||||||
|
Note that DMARC policy of the sender domain can request more strict handling of
|
||||||
|
broken DKIM signatures.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### fail_open _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Whether to accept the message if a temporary error occurs during DKIM
|
||||||
|
verification. Rejecting the message with a 4xx code will require the sender
|
||||||
|
to resend it later in a hope that the problem will be resolved.
|
173
docs/reference/checks/dnsbl.md
Normal file
173
docs/reference/checks/dnsbl.md
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
# DNSBL lookup
|
||||||
|
|
||||||
|
The check.dnsbl module implements checking of source IP and hostnames against a set
|
||||||
|
of DNS-based Blackhole lists (DNSBLs).
|
||||||
|
|
||||||
|
Its configuration consists of module configuration directives and a set
|
||||||
|
of blocks specifying lists to use and kind of lookups to perform on them.
|
||||||
|
|
||||||
|
```
|
||||||
|
check.dnsbl {
|
||||||
|
debug no
|
||||||
|
check_early no
|
||||||
|
|
||||||
|
quarantine_threshold 1
|
||||||
|
reject_threshold 1
|
||||||
|
|
||||||
|
# Lists configuration example.
|
||||||
|
dnsbl.example.org {
|
||||||
|
client_ipv4 yes
|
||||||
|
client_ipv6 no
|
||||||
|
ehlo no
|
||||||
|
mailfrom no
|
||||||
|
score 1
|
||||||
|
}
|
||||||
|
hsrbl.example.org {
|
||||||
|
client_ipv4 no
|
||||||
|
client_ipv6 no
|
||||||
|
ehlo yes
|
||||||
|
mailfrom yes
|
||||||
|
score 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
Arguments specify the list of IP-based BLs to use.
|
||||||
|
|
||||||
|
The following configurations are equivalent.
|
||||||
|
|
||||||
|
```
|
||||||
|
check {
|
||||||
|
dnsbl dnsbl.example.org dnsbl2.example.org
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
check {
|
||||||
|
dnsbl {
|
||||||
|
dnsbl.example.org dnsbl2.example.org {
|
||||||
|
client_ipv4 yes
|
||||||
|
client_ipv6 no
|
||||||
|
ehlo no
|
||||||
|
mailfrom no
|
||||||
|
score 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
Enable verbose logging.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### check_early _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Check BLs before mail delivery starts and silently reject blacklisted clients.
|
||||||
|
|
||||||
|
For this to work correctly, check should not be used in source/destination
|
||||||
|
pipeline block.
|
||||||
|
|
||||||
|
In particular, this means:
|
||||||
|
|
||||||
|
- No logging is done for rejected messages.
|
||||||
|
- No action is taken if `quarantine_threshold` is hit, only `reject_threshold`
|
||||||
|
applies.
|
||||||
|
- `defer_sender_reject` from SMTP configuration takes no effect.
|
||||||
|
- MAIL FROM is not checked, even if specified.
|
||||||
|
|
||||||
|
If you often get hit by spam attacks, it is recommended to enable this
|
||||||
|
setting to save server resources.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### quarantine_threshold _integer_
|
||||||
|
Default: `1`
|
||||||
|
|
||||||
|
DNSBL score needed (equals-or-higher) to quarantine the message.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### reject_threshold _integer_
|
||||||
|
Default: `9999`
|
||||||
|
|
||||||
|
DNSBL score needed (equals-or-higher) to reject the message.
|
||||||
|
|
||||||
|
## List configuration
|
||||||
|
|
||||||
|
```
|
||||||
|
dnsbl.example.org dnsbl.example.com {
|
||||||
|
client_ipv4 yes
|
||||||
|
client_ipv6 no
|
||||||
|
ehlo no
|
||||||
|
mailfrom no
|
||||||
|
responses 127.0.0.1/24
|
||||||
|
score 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Directive name and arguments specify the actual DNS zone to query when checking
|
||||||
|
the list. Using multiple arguments is equivalent to specifying the same
|
||||||
|
configuration separately for each list.
|
||||||
|
|
||||||
|
### client_ipv4 _boolean_
|
||||||
|
Default: `yes`
|
||||||
|
|
||||||
|
Whether to check address of the IPv4 clients against the list.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### client_ipv6 _boolean_
|
||||||
|
Default: `yes`
|
||||||
|
|
||||||
|
Whether to check address of the IPv6 clients against the list.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ehlo _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Whether to check hostname specified n the HELO/EHLO command
|
||||||
|
against the list.
|
||||||
|
|
||||||
|
This works correctly only with domain-based DNSBLs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### mailfrom _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Whether to check domain part of the MAIL FROM address against the list.
|
||||||
|
|
||||||
|
This works correctly only with domain-based DNSBLs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### responses _cidr_ | _ip..._
|
||||||
|
Default: `127.0.0.1/24`
|
||||||
|
|
||||||
|
IP networks (in CIDR notation) or addresses to permit in list lookup results.
|
||||||
|
Addresses not matching any entry in this directives will be ignored.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### score _integer_
|
||||||
|
Default: `1`
|
||||||
|
|
||||||
|
Score value to add for the message if it is listed.
|
||||||
|
|
||||||
|
If sum of list scores is equals or higher than `quarantine_threshold`, the
|
||||||
|
message will be quarantined.
|
||||||
|
|
||||||
|
If sum of list scores is equals or higher than `rejected_threshold`, the message
|
||||||
|
will be rejected.
|
||||||
|
|
||||||
|
It is possible to specify a negative value to make list act like a whitelist
|
||||||
|
and override results of other blocklists.
|
49
docs/reference/checks/milter.md
Normal file
49
docs/reference/checks/milter.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Milter client
|
||||||
|
|
||||||
|
The 'milter' implements subset of Sendmail's milter protocol that can be used
|
||||||
|
to integrate external software with maddy.
|
||||||
|
maddy implements version 6 of the protocol, older versions are
|
||||||
|
not supported.
|
||||||
|
|
||||||
|
Notable limitations of protocol implementation in maddy include:
|
||||||
|
1. Changes of envelope sender address are not supported
|
||||||
|
2. Removal and addition of envelope recipients is not supported
|
||||||
|
3. Removal and replacement of header fields is not supported
|
||||||
|
4. Headers fields can be inserted only on top
|
||||||
|
5. Milter does not receive some "macros" provided by sendmail.
|
||||||
|
|
||||||
|
Restrictions 1 and 2 are inherent to the maddy checks interface and cannot be
|
||||||
|
removed without major changes to it. Restrictions 3, 4 and 5 are temporary due to
|
||||||
|
incomplete implementation.
|
||||||
|
|
||||||
|
```
|
||||||
|
check.milter {
|
||||||
|
endpoint <endpoint>
|
||||||
|
fail_open false
|
||||||
|
}
|
||||||
|
|
||||||
|
milter <endpoint>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
When defined inline, the first argument specifies endpoint to access milter
|
||||||
|
via. See below.
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### endpoint _scheme://path_
|
||||||
|
Default: not set
|
||||||
|
|
||||||
|
Specifies milter protocol endpoint to use.
|
||||||
|
The endpoit is specified in standard URL-like format:
|
||||||
|
`tcp://127.0.0.1:6669` or `unix:///var/lib/milter/filter.sock`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### fail_open _boolean_
|
||||||
|
Default: `false`
|
||||||
|
|
||||||
|
Toggles behavior on milter I/O errors. If false ("fail closed") - message is
|
||||||
|
rejected with temporary error code. If true ("fail open") - check is skipped.
|
||||||
|
|
48
docs/reference/checks/misc.md
Normal file
48
docs/reference/checks/misc.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Misc checks
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
Following directives are defined for all modules listed below.
|
||||||
|
|
||||||
|
### fail_action `ignore` | `reject` | `quarantine`
|
||||||
|
Default: `quarantine`
|
||||||
|
|
||||||
|
Action to take when check fails. See [Check actions](../actions/) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
Log both successful and unsuccessful check executions instead of just
|
||||||
|
unsuccessful.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### require_mx_record
|
||||||
|
|
||||||
|
Check that domain in MAIL FROM command does have a MX record and none of them
|
||||||
|
are "null" (contain a single dot as the host).
|
||||||
|
|
||||||
|
By default, quarantines messages coming from servers missing MX records,
|
||||||
|
use `fail_action` directive to change that.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### require_matching_rdns
|
||||||
|
|
||||||
|
Check that source server IP does have a PTR record point to the domain
|
||||||
|
specified in EHLO/HELO command.
|
||||||
|
|
||||||
|
By default, quarantines messages coming from servers with mismatched or missing
|
||||||
|
PTR record, use `fail_action` directive to change that.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### require_tls
|
||||||
|
|
||||||
|
Check that the source server is connected via TLS; either directly, or by using
|
||||||
|
the STARTTLS command.
|
||||||
|
|
||||||
|
By default, rejects messages coming from unencrypted servers. Use the
|
||||||
|
`fail_action` directive to change that.
|
97
docs/reference/checks/rspamd.md
Normal file
97
docs/reference/checks/rspamd.md
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# rspamd
|
||||||
|
|
||||||
|
The 'rspamd' module implements message filtering by contacting the rspamd
|
||||||
|
server via HTTP API.
|
||||||
|
|
||||||
|
```
|
||||||
|
check.rspamd {
|
||||||
|
tls_client { ... }
|
||||||
|
api_path http://127.0.0.1:11333
|
||||||
|
settings_id whatever
|
||||||
|
tag maddy
|
||||||
|
hostname mx.example.org
|
||||||
|
io_error_action ignore
|
||||||
|
error_resp_action ignore
|
||||||
|
add_header_action quarantine
|
||||||
|
rewrite_subj_action quarantine
|
||||||
|
flags pass_all
|
||||||
|
}
|
||||||
|
|
||||||
|
rspamd http://127.0.0.1:11333
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### tls_client { ... }
|
||||||
|
Default: not set
|
||||||
|
|
||||||
|
Configure TLS client if HTTPS is used. See [TLS configuration / Client](/reference/tls/#client) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### api_path _url_
|
||||||
|
Default: `http://127.0.0.1:11333`
|
||||||
|
|
||||||
|
URL of HTTP API endpoint. Supports both HTTP and HTTPS and can include
|
||||||
|
path element.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### settings_id _string_
|
||||||
|
Default: not set
|
||||||
|
|
||||||
|
Settings ID to pass to the server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### tag _string_
|
||||||
|
Default: `maddy`
|
||||||
|
|
||||||
|
Value to send in MTA-Tag header field.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### hostname _string_ <br>
|
||||||
|
Default: value of global directive
|
||||||
|
|
||||||
|
Value to send in MTA-Name header field.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### io_error_action _action_
|
||||||
|
Default: `ignore`
|
||||||
|
|
||||||
|
Action to take in case of inability to contact the rspamd server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### error_resp_action _action_
|
||||||
|
Default: `ignore`
|
||||||
|
|
||||||
|
Action to take in case of 5xx or 4xx response received from the rspamd server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### add_header_action _action_
|
||||||
|
Default: `quarantine`
|
||||||
|
|
||||||
|
Action to take when rspamd requests to "add header".
|
||||||
|
|
||||||
|
X-Spam-Flag and X-Spam-Score are added to the header irregardless of value.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### rewrite_subj_action _action_
|
||||||
|
Default: `quarantine`
|
||||||
|
|
||||||
|
Action to take when rspamd requests to "rewrite subject".
|
||||||
|
|
||||||
|
X-Spam-Flag and X-Spam-Score are added to the header irregardless of value.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### flags _string-list..._
|
||||||
|
Default: `pass_all`
|
||||||
|
|
||||||
|
Flags to pass to the rspamd server.
|
||||||
|
See [https://rspamd.com/doc/architecture/protocol.html](https://rspamd.com/doc/architecture/protocol.html) for details.
|
97
docs/reference/checks/spf.md
Normal file
97
docs/reference/checks/spf.md
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# SPF
|
||||||
|
|
||||||
|
check.spf the check module that verifies whether IP address of the client is
|
||||||
|
authorized to send messages for domain in MAIL FROM address.
|
||||||
|
|
||||||
|
SPF statuses are mapped to maddy check actions in a way
|
||||||
|
specified by \*_action directives. By default, SPF failure
|
||||||
|
results in the message being quarantined and errors (both permanent and
|
||||||
|
temporary) cause message to be rejected.
|
||||||
|
Authentication-Results field is generated irregardless of status.
|
||||||
|
|
||||||
|
## DMARC override
|
||||||
|
|
||||||
|
It is recommended by the DMARC standard to don't fail delivery based solely on
|
||||||
|
SPF policy and always check DMARC policy and take action based on it.
|
||||||
|
|
||||||
|
If `enforce_early` is `no`, check.spf module will not take any action on SPF
|
||||||
|
policy failure if sender domain does have a DMARC record with 'quarantine' or
|
||||||
|
'reject' policy. Instead it will rely on DMARC support to take necesary
|
||||||
|
actions using SPF results as an input.
|
||||||
|
|
||||||
|
Disabling `enforce_early` without enabling DMARC support will make SPF policies
|
||||||
|
no-op and is considered insecure.
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
```
|
||||||
|
check.spf {
|
||||||
|
debug no
|
||||||
|
enforce_early no
|
||||||
|
fail_action quarantine
|
||||||
|
softfail_action ignore
|
||||||
|
permerr_action reject
|
||||||
|
temperr_action reject
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
Enable verbose logging for check.spf.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### enforce_early _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Make policy decision on MAIL FROM stage (before the message body is received).
|
||||||
|
This makes it impossible to apply DMARC override (see above).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### none_action `reject` | `quarantine` | `ignore`
|
||||||
|
Default: `ignore`
|
||||||
|
|
||||||
|
Action to take when SPF policy evaluates to a 'none' result.
|
||||||
|
|
||||||
|
See [https://tools.ietf.org/html/rfc7208#section-2.6](https://tools.ietf.org/html/rfc7208#section-2.6) for meaning of
|
||||||
|
SPF results.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### neutral_action `reject` | `quarantine` | `ignore`
|
||||||
|
Default: `ignore`
|
||||||
|
|
||||||
|
Action to take when SPF policy evaluates to a 'neutral' result.
|
||||||
|
|
||||||
|
See [https://tools.ietf.org/html/rfc7208#section-2.6](https://tools.ietf.org/html/rfc7208#section-2.6) for meaning of
|
||||||
|
SPF results.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### fail_action `reject` | `quarantine` | `ignore`
|
||||||
|
Default: `quarantine`
|
||||||
|
|
||||||
|
Action to take when SPF policy evaluates to a 'fail' result.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### softfail_action `reject` | `quarantine` | `ignore`
|
||||||
|
Default: `ignore`
|
||||||
|
|
||||||
|
Action to take when SPF policy evaluates to a 'softfail' result.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### permerr_action `reject` | `quarantine` | `ignore`
|
||||||
|
Default: `reject`
|
||||||
|
|
||||||
|
Action to take when SPF policy evaluates to a 'permerror' result.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### temperr_action `reject` | `quarantine` | `ignore`
|
||||||
|
Default: `reject`
|
||||||
|
|
||||||
|
Action to take when SPF policy evaluates to a 'temperror' result.
|
200
docs/reference/config-syntax.md
Normal file
200
docs/reference/config-syntax.md
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
# Configuration files syntax
|
||||||
|
|
||||||
|
**Note:** This file is a technical document describing how
|
||||||
|
maddy parses configuration files.
|
||||||
|
|
||||||
|
Configuration consists of newline-delimited "directives". Each directive can
|
||||||
|
have zero or more arguments.
|
||||||
|
|
||||||
|
```
|
||||||
|
directive0
|
||||||
|
directive1 arg0 arg1
|
||||||
|
```
|
||||||
|
|
||||||
|
Any line starting with # is ignored. Empty lines are ignored too.
|
||||||
|
|
||||||
|
## Quoting
|
||||||
|
|
||||||
|
Strings with whitespace should be wrapped into double quotes to make sure they
|
||||||
|
will be interpreted as a single argument.
|
||||||
|
|
||||||
|
```
|
||||||
|
directive0 two arguments
|
||||||
|
directive1 "one argument"
|
||||||
|
```
|
||||||
|
|
||||||
|
String wrapped in quotes may contain newlines and they will not be interpreted
|
||||||
|
as a directive separator.
|
||||||
|
|
||||||
|
```
|
||||||
|
directive0 "one long big
|
||||||
|
argument for directive0"
|
||||||
|
```
|
||||||
|
|
||||||
|
Quotes and only quotes can be escaped inside literals: \\"
|
||||||
|
|
||||||
|
Backslash can be used at the end of line to continue the directve on the next
|
||||||
|
line.
|
||||||
|
|
||||||
|
## Blocks
|
||||||
|
|
||||||
|
A directive may have several subdirectives. They are written in a {-enclosed
|
||||||
|
block like this:
|
||||||
|
```
|
||||||
|
directive0 arg0 arg1 {
|
||||||
|
subdirective0 arg0 arg1
|
||||||
|
subdirective1 etc
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Subdirectives can have blocks too.
|
||||||
|
|
||||||
|
```
|
||||||
|
directive0 {
|
||||||
|
subdirective0 {
|
||||||
|
subdirective2 {
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subdirective1 { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Level of nesting is limited, but you should never hit the limit with correct
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
In most cases, an empty block is equivalent to no block:
|
||||||
|
```
|
||||||
|
directive { }
|
||||||
|
directive2 # same as above
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
Environment variables can be referenced in the configuration using either
|
||||||
|
{env:VARIABLENAME} syntax.
|
||||||
|
|
||||||
|
Non-existent variables are expanded to empty strings and not removed from
|
||||||
|
the arguments list. In the following example, directive0 will have one argument
|
||||||
|
independently of whether VAR is defined.
|
||||||
|
|
||||||
|
```
|
||||||
|
directive0 {env:VAR}
|
||||||
|
```
|
||||||
|
|
||||||
|
Parse is forgiving and incomplete variable placeholder (e.g. '{env:VAR') will
|
||||||
|
be left as-is. Variables are expanded inside quotes too.
|
||||||
|
|
||||||
|
## Snippets & imports
|
||||||
|
|
||||||
|
You can reuse blocks of configuration by defining them as "snippets". Snippet
|
||||||
|
is just a directive with a block, declared tp top level (not inside any blocks)
|
||||||
|
and with a directive name wrapped in curly braces.
|
||||||
|
|
||||||
|
```
|
||||||
|
(snippetname) {
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The snippet can then be referenced using 'import' meta-directive.
|
||||||
|
|
||||||
|
```
|
||||||
|
unrelated0
|
||||||
|
unrelated1
|
||||||
|
import snippetname
|
||||||
|
```
|
||||||
|
|
||||||
|
The above example will be expanded into the following configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
unrelated0
|
||||||
|
unrelated1
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
```
|
||||||
|
|
||||||
|
Import statement also can be used to include content from other files. It works
|
||||||
|
exactly the same way as with snippets but the file path should be used instead.
|
||||||
|
The path can be either relative to the location of the currently processed
|
||||||
|
configuration file or absolute. If there are both snippet and file with the
|
||||||
|
same name - snippet will be used.
|
||||||
|
|
||||||
|
```
|
||||||
|
# /etc/maddy/tls.conf
|
||||||
|
tls long_path_to_certificate long_path_to_private_key
|
||||||
|
|
||||||
|
# /etc/maddy/maddy.conf
|
||||||
|
smtp tcp://0.0.0.0:25 {
|
||||||
|
import tls.conf
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# Expanded into:
|
||||||
|
smtp tcp://0.0.0.0:25 {
|
||||||
|
tls long_path_to_certificate long_path_to_private_key
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The imported file can introduce new snippets and they can be referenced in any
|
||||||
|
processed configuration file.
|
||||||
|
|
||||||
|
## Duration values
|
||||||
|
|
||||||
|
Directives that accept duration use the following format: A sequence of decimal
|
||||||
|
digits with an optional fraction and unit suffix (zero can be specified without
|
||||||
|
a suffix). If multiple values are specified, they will be added.
|
||||||
|
|
||||||
|
Valid unit suffixes: "h" (hours), "m" (minutes), "s" (seconds), "ms" (milliseconds).
|
||||||
|
Implementation also accepts us and ns for microseconds and nanoseconds, but these
|
||||||
|
values are useless in practice.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```
|
||||||
|
1h
|
||||||
|
1h 5m
|
||||||
|
1h5m
|
||||||
|
0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data size values
|
||||||
|
|
||||||
|
Similar to duration values, but fractions are not allowed and suffixes are different.
|
||||||
|
|
||||||
|
Valid unit suffixes: "G" (gibibyte, 1024^3 bytes), "M" (mebibyte, 1024^2 bytes),
|
||||||
|
"K" (kibibyte, 1024 bytes), "B" or "b" (byte).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```
|
||||||
|
32M
|
||||||
|
3M 5K
|
||||||
|
5b
|
||||||
|
```
|
||||||
|
|
||||||
|
Also note that the following is not valid, unlike Duration values syntax:
|
||||||
|
```
|
||||||
|
32M5K
|
||||||
|
```
|
||||||
|
|
||||||
|
## Address Definitions
|
||||||
|
|
||||||
|
Maddy configuration uses URL-like syntax to specify network addresses.
|
||||||
|
|
||||||
|
- `unix://file_path` – Unix domain socket. Relative paths are relative to runtime directory (`/run/maddy`).
|
||||||
|
- `tcp://ADDRESS:PORT` – TCP/IP socket.
|
||||||
|
- `tls://ADDRESS:PORT` – TCP/IP socket using TLS.
|
||||||
|
|
||||||
|
## Dummy Module
|
||||||
|
|
||||||
|
No-op module. It doesn't need to be configured explicitly and can be referenced
|
||||||
|
using "dummy" name. It can act as a delivery target or auth.
|
||||||
|
provider. In the latter case, it will accept any credentials, allowing any
|
||||||
|
client to authenticate using any username and password (use with care!).
|
||||||
|
|
||||||
|
|
164
docs/reference/endpoints/imap.md
Normal file
164
docs/reference/endpoints/imap.md
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# IMAP4rev1 endpoint
|
||||||
|
|
||||||
|
Module 'imap' is a listener that implements IMAP4rev1 protocol and provides
|
||||||
|
access to local messages storage specified by 'storage' directive.
|
||||||
|
|
||||||
|
In most cases, local storage modules will auto-create accounts when they are
|
||||||
|
accessed via IMAP. This relies on authentication provider used by IMAP endpoint
|
||||||
|
to provide what essentially is access control. There is a caveat, however: this
|
||||||
|
auto-creation will not happen when delivering incoming messages via SMTP as
|
||||||
|
there is no authentication to confirm that this account should indeed be
|
||||||
|
created.
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
```
|
||||||
|
imap tcp://0.0.0.0:143 tls://0.0.0.0:993 {
|
||||||
|
tls /etc/ssl/private/cert.pem /etc/ssl/private/pkey.key
|
||||||
|
io_debug no
|
||||||
|
debug no
|
||||||
|
insecure_auth no
|
||||||
|
sasl_login no
|
||||||
|
auth pam
|
||||||
|
storage &local_mailboxes
|
||||||
|
auth_map identity
|
||||||
|
auth_map_normalize auto
|
||||||
|
storage_map identity
|
||||||
|
storage_map_normalize auto
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### tls _certificate-path_ _key-path_ { ... }
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
TLS certificate & key to use. Fine-tuning of other TLS properties is possible
|
||||||
|
by specifying a configuration block and options inside it:
|
||||||
|
|
||||||
|
```
|
||||||
|
tls cert.crt key.key {
|
||||||
|
protocols tls1.2 tls1.3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [TLS configuration / Server](/reference/tls/#server-side) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### proxy_protocol _trusted ips..._ { ... }
|
||||||
|
Default: not enabled
|
||||||
|
|
||||||
|
Enable use of HAProxy PROXY protocol. Supports both v1 and v2 protocols.
|
||||||
|
If a list of trusted IP addresses or subnets is provided, only connections
|
||||||
|
from those will be trusted.
|
||||||
|
|
||||||
|
TLS for the channel between the proxies and maddy can be configured
|
||||||
|
using a 'tls' directive:
|
||||||
|
```
|
||||||
|
proxy_protocol {
|
||||||
|
trust 127.0.0.1 ::1 192.168.0.1/24
|
||||||
|
tls &proxy_tls
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Note that the top-level 'tls' directive is not inherited here. If you
|
||||||
|
need TLS on top of the PROXY protocol, securing the protocol header,
|
||||||
|
you must declare TLS explicitly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### io_debug _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Write all commands and responses to stderr.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### io_errors _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Log I/O errors.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
Enable verbose logging.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### insecure_auth _boolean_
|
||||||
|
Default: `no` (`yes` if TLS is disabled)
|
||||||
|
|
||||||
|
Allow plain-text authentication over unencrypted connections.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### sasl_login _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Enable support for SASL LOGIN authentication mechanism used by
|
||||||
|
some outdated clients.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### auth _module-reference_
|
||||||
|
**Required.**
|
||||||
|
|
||||||
|
Use the specified module for authentication.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### storage _module-reference_
|
||||||
|
**Required.**
|
||||||
|
|
||||||
|
Use the specified module for message storage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### storage_map _module-reference_
|
||||||
|
Default: `identity`
|
||||||
|
|
||||||
|
Use the specified table to map SASL usernames to storage account names.
|
||||||
|
|
||||||
|
Before username is looked up, it is normalized using function defined by
|
||||||
|
`storage_map_normalize`.
|
||||||
|
|
||||||
|
This directive is useful if you want users user@example.org and user@example.com
|
||||||
|
to share the same storage account named "user". In this case, use
|
||||||
|
|
||||||
|
```
|
||||||
|
storage_map email_localpart
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that `storage_map` does not affect the username passed to the
|
||||||
|
authentication provider.
|
||||||
|
|
||||||
|
It also does not affect how message delivery is handled, you should specify
|
||||||
|
`delivery_map` in storage module to define how to map email addresses
|
||||||
|
to storage accounts. E.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
storage.imapsql local_mailboxes {
|
||||||
|
...
|
||||||
|
delivery_map email_localpart # deliver "user@*" to mailbox for "user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### storage_map_normalize _function_
|
||||||
|
Default: `auto`
|
||||||
|
|
||||||
|
Same as `auth_map_normalize` but for `storage_map`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### auth_map_normalize _function_
|
||||||
|
Default: `auto`
|
||||||
|
|
||||||
|
Overrides global `auth_map_normalize` value for this endpoint.
|
||||||
|
|
||||||
|
See [Global configuration](/reference/global-config) for details.
|
||||||
|
|
||||||
|
|
||||||
|
|
41
docs/reference/endpoints/openmetrics.md
Normal file
41
docs/reference/endpoints/openmetrics.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# OpenMetrics/Prometheus telemetry
|
||||||
|
|
||||||
|
Various server statistics are provided in OpenMetrics format by the
|
||||||
|
"openmetrics" module.
|
||||||
|
|
||||||
|
To enable it, add the following line to the server config:
|
||||||
|
|
||||||
|
```
|
||||||
|
openmetrics tcp://127.0.0.1:9749 { }
|
||||||
|
```
|
||||||
|
|
||||||
|
Scrape endpoint would be `http://127.0.0.1:9749/metrics`.
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
```
|
||||||
|
# AUTH command failures due to invalid credentials.
|
||||||
|
maddy_smtp_failed_logins{module}
|
||||||
|
# Failed SMTP transaction commands (MAIL, RCPT, DATA).
|
||||||
|
maddy_smtp_failed_commands{module, command, smtp_code, smtp_enchcode}
|
||||||
|
# Messages rejected with 4xx code due to ratelimiting.
|
||||||
|
maddy_smtp_ratelimit_deferred{module}
|
||||||
|
# Amount of started SMTP transactions started.
|
||||||
|
maddy_smtp_started_transactions{module}
|
||||||
|
# Amount of aborted SMTP transactions started.
|
||||||
|
maddy_smtp_aborted_transactions{module}
|
||||||
|
# Amount of completed SMTP transactions.
|
||||||
|
maddy_smtp_completed_transactions{module}
|
||||||
|
# Number of times a check returned 'reject' result (may be more than processed
|
||||||
|
# messages if check does so on per-recipient basis).
|
||||||
|
maddy_check_reject{check}
|
||||||
|
# Number of times a check returned 'quarantine' result (may be more than
|
||||||
|
# processed messages if check does so on per-recipient basis).
|
||||||
|
maddy_check_quarantined{check}
|
||||||
|
# Amount of queued messages.
|
||||||
|
maddy_queue_length{module, location}
|
||||||
|
# Outbound connections established with specific TLS security level.
|
||||||
|
maddy_remote_conns_tls_level{module, level}
|
||||||
|
# Outbound connections established with specific MX security level.
|
||||||
|
maddy_remote_conns_mx_level{module, level}
|
||||||
|
```
|
312
docs/reference/endpoints/smtp.md
Normal file
312
docs/reference/endpoints/smtp.md
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
# SMTP/LMTP/Submission endpoint
|
||||||
|
|
||||||
|
Module 'smtp' is a listener that implements ESMTP protocol with optional
|
||||||
|
authentication, LMTP and Submission support. Incoming messages are processed in
|
||||||
|
accordance with pipeline rules (explained in Message pipeline section below).
|
||||||
|
|
||||||
|
```
|
||||||
|
smtp tcp://0.0.0.0:25 {
|
||||||
|
hostname example.org
|
||||||
|
tls /etc/ssl/private/cert.pem /etc/ssl/private/pkey.key
|
||||||
|
io_debug no
|
||||||
|
debug no
|
||||||
|
insecure_auth no
|
||||||
|
sasl_login no
|
||||||
|
read_timeout 10m
|
||||||
|
write_timeout 1m
|
||||||
|
max_message_size 32M
|
||||||
|
max_header_size 1M
|
||||||
|
auth pam
|
||||||
|
defer_sender_reject yes
|
||||||
|
dmarc yes
|
||||||
|
smtp_max_line_length 4000
|
||||||
|
limits {
|
||||||
|
endpoint rate 10
|
||||||
|
endpoint concurrency 500
|
||||||
|
}
|
||||||
|
|
||||||
|
# Example pipeline ocnfiguration.
|
||||||
|
destination example.org {
|
||||||
|
deliver_to &local_mailboxes
|
||||||
|
}
|
||||||
|
default_destination {
|
||||||
|
reject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
### hostname _string_
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
Server name to use in SMTP banner.
|
||||||
|
|
||||||
|
```
|
||||||
|
220 example.org ESMTP Service Ready
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### tls _certificate-path_ _key-path_ { ... }
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
TLS certificate & key to use. Fine-tuning of other TLS properties is possible
|
||||||
|
by specifying a configuration block and options inside it:
|
||||||
|
|
||||||
|
```
|
||||||
|
tls cert.crt key.key {
|
||||||
|
protocols tls1.2 tls1.3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [TLS configuration / Server](/reference/tls/#server-side) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### proxy_protocol _trusted ips..._ { ... } <br>
|
||||||
|
Default: not enabled
|
||||||
|
|
||||||
|
Enable use of HAProxy PROXY protocol. Supports both v1 and v2 protocols.
|
||||||
|
If a list of trusted IP addresses or subnets is provided, only connections
|
||||||
|
from those will be trusted.
|
||||||
|
|
||||||
|
TLS for the channel between the proxies and maddy can be configured
|
||||||
|
using a 'tls' directive:
|
||||||
|
```
|
||||||
|
proxy_protocol {
|
||||||
|
trust 127.0.0.1 ::1 192.168.0.1/24
|
||||||
|
tls &proxy_tls
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### io_debug _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Write all commands and responses to stderr.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
Enable verbose logging.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### insecure_auth _boolean_
|
||||||
|
Default: `no` (`yes` if TLS is disabled)
|
||||||
|
|
||||||
|
Allow plain-text authentication over unencrypted connections. Not recommended!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### sasl_login _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Enable support for SASL LOGIN authentication mechanism used by
|
||||||
|
some outdated clients.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### read_timeout _duration_
|
||||||
|
Default: `10m`
|
||||||
|
|
||||||
|
I/O read timeout.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### write_timeout _duration_
|
||||||
|
Default: `1m`
|
||||||
|
|
||||||
|
I/O write timeout.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### max_message_size _size_
|
||||||
|
Default: `32M`
|
||||||
|
|
||||||
|
Limit the size of incoming messages to 'size'.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### max_header_size _size_
|
||||||
|
Default: `1M`
|
||||||
|
|
||||||
|
Limit the size of incoming message headers to 'size'.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### auth _module-reference_
|
||||||
|
Default: not specified
|
||||||
|
|
||||||
|
Use the specified module for authentication.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### defer_sender_reject _boolean_
|
||||||
|
Default: `yes`
|
||||||
|
|
||||||
|
Apply sender-based checks and routing logic when first RCPT TO command
|
||||||
|
is received. This allows maddy to log recipient address of the rejected
|
||||||
|
message and also improves interoperability with (improperly implemented)
|
||||||
|
clients that don't expect an error early in session.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### max_logged_rcpt_errors _integer_
|
||||||
|
Default: `5`
|
||||||
|
|
||||||
|
Amount of RCPT-time errors that should be logged. Further errors will be
|
||||||
|
handled silently. This is to prevent log flooding during email dictionary
|
||||||
|
attacks (address probing).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### max_received _integer_
|
||||||
|
Default: `50`
|
||||||
|
|
||||||
|
Max. amount of Received header fields in the message header. If the incoming
|
||||||
|
message has more fields than this number, it will be rejected with the permanent error
|
||||||
|
5.4.6 ("Routing loop detected").
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### buffer `ram`<br>buffer `fs` _path_ <br>buffer `auto` _max-size_ _path_
|
||||||
|
Default: `auto 1M StateDirectory/buffer`
|
||||||
|
|
||||||
|
Temporary storage to use for the body of accepted messages.
|
||||||
|
|
||||||
|
- `ram` – Store the body in RAM.
|
||||||
|
- `fs` – Write out the message to the FS and read it back as needed.
|
||||||
|
_path_ can be omitted and defaults to StateDirectory/buffer.
|
||||||
|
- `auto` – Store message bodies smaller than `_max_size_` entirely in RAM,
|
||||||
|
otherwise write them out to the FS. _path_ can be omitted and defaults to `StateDirectory/buffer`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### smtp_max_line_length _integer_
|
||||||
|
Default: `4000`
|
||||||
|
|
||||||
|
The maximum line length allowed in the SMTP input stream. If client sends a
|
||||||
|
longer line - connection will be closed and message (if any) will be rejected
|
||||||
|
with a permanent error.
|
||||||
|
|
||||||
|
RFC 5321 has the recommended limit of 998 bytes. Servers are not required
|
||||||
|
to handle longer lines correctly but some senders may produce them.
|
||||||
|
|
||||||
|
Unless BDAT extension is used by the sender, this limitation also applies to
|
||||||
|
the message body.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### dmarc _boolean_
|
||||||
|
Default: `yes`
|
||||||
|
|
||||||
|
Enforce sender's DMARC policy. Due to implementation limitations, it is not a
|
||||||
|
check module.
|
||||||
|
|
||||||
|
**Note**: Report generation is not implemented now.
|
||||||
|
|
||||||
|
**Note**: DMARC needs SPF and DKIM checks to function correctly.
|
||||||
|
Without these, DMARC check will not run.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rate & concurrency limiting
|
||||||
|
|
||||||
|
### limits { ... }
|
||||||
|
Default: no limits
|
||||||
|
|
||||||
|
This allows configuring a set of message flow restrictions including
|
||||||
|
max. concurrency and rate per-endpoint, per-source, per-destination.
|
||||||
|
|
||||||
|
Limits are specified as directives inside the block:
|
||||||
|
|
||||||
|
```
|
||||||
|
limits {
|
||||||
|
all rate 20
|
||||||
|
destination concurrency 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported limits:
|
||||||
|
|
||||||
|
### _scope_ rate _burst_ _period_
|
||||||
|
|
||||||
|
Rate limit. Restrict the amount of messages processed in _period_ to
|
||||||
|
_burst_ messages. If period is not specified, 1 second is used.
|
||||||
|
|
||||||
|
### _scope_ concurrency _max_
|
||||||
|
Concurrency limit. Restrict the amount of messages processed in parallel
|
||||||
|
to _max_.
|
||||||
|
|
||||||
|
For each supported limitation, _scope_ determines whether it should be applied
|
||||||
|
for all messages ("all"), per-sender IP ("ip"), per-sender domain ("source") or
|
||||||
|
per-recipient domain ("destination"). Having a scope other than "all" means
|
||||||
|
that the restriction will be enforced independently for each group determined
|
||||||
|
by scope. E.g. "ip rate 20" means that the same IP cannot send more than 20
|
||||||
|
messages per second. "destination concurrency 5" means that no more than 5
|
||||||
|
messages can be sent in parallel to a single domain.
|
||||||
|
|
||||||
|
**Note**: At the moment, SMTP endpoint on its own does not support per-recipient
|
||||||
|
limits. They will be no-op. If you want to enforce a per-recipient restriction
|
||||||
|
on outbound messages, do so using 'limits' directive for the 'table.remote' module
|
||||||
|
|
||||||
|
It is possible to share limit counters between multiple endpoints (or any other
|
||||||
|
modules). To do so define a top-level configuration block for module "limits"
|
||||||
|
and reference it where needed using standard & syntax. E.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
limits inbound_limits {
|
||||||
|
all rate 20
|
||||||
|
}
|
||||||
|
|
||||||
|
smtp smtp://0.0.0.0:25 {
|
||||||
|
limits &inbound_limits
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
submission tls://0.0.0.0:465 {
|
||||||
|
limits &inbound_limits
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Using an "all rate" restriction in such way means that no more than 20
|
||||||
|
messages can enter the server through both endpoints in one second.
|
||||||
|
|
||||||
|
# Submission module (submission)
|
||||||
|
|
||||||
|
Module 'submission' implements all functionality of the 'smtp' module and adds
|
||||||
|
certain message preprocessing on top of it, additionally authentication is
|
||||||
|
always required.
|
||||||
|
|
||||||
|
'submission' module checks whether addresses in header fields From, Sender, To,
|
||||||
|
Cc, Bcc, Reply-To are correct and adds Message-ID and Date if it is missing.
|
||||||
|
|
||||||
|
```
|
||||||
|
submission tcp://0.0.0.0:587 tls://0.0.0.0:465 {
|
||||||
|
# ... same as smtp ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# LMTP module (lmtp)
|
||||||
|
|
||||||
|
Module 'lmtp' implements all functionality of the 'smtp' module but uses
|
||||||
|
LMTP (RFC 2033) protocol.
|
||||||
|
|
||||||
|
```
|
||||||
|
lmtp unix://lmtp.sock {
|
||||||
|
# ... same as smtp ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations of LMTP implementation
|
||||||
|
|
||||||
|
- Can't be used with TCP.
|
||||||
|
- Delivery to 'sql' module storage is always atomic, either all recipients will
|
||||||
|
succeed or none of them will.
|
||||||
|
|
153
docs/reference/global-config.md
Normal file
153
docs/reference/global-config.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# Global configuration directives
|
||||||
|
|
||||||
|
These directives can be specified outside of any
|
||||||
|
configuration blocks and they are applied to all modules.
|
||||||
|
|
||||||
|
Some directives can be overridden on per-module basis (e.g. hostname).
|
||||||
|
|
||||||
|
### state_dir _path_
|
||||||
|
Default: `/var/lib/maddy`
|
||||||
|
|
||||||
|
The path to the state directory. This directory will be used to store all
|
||||||
|
persistent data and should be writable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### runtime_dir _path_
|
||||||
|
Default: `/run/maddy`
|
||||||
|
|
||||||
|
The path to the runtime directory. Used for Unix sockets and other temporary
|
||||||
|
objects. Should be writable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### hostname _domain_
|
||||||
|
Default: not specified
|
||||||
|
|
||||||
|
Internet hostname of this mail server. Typicall FQDN is used. It is recommended
|
||||||
|
to make sure domain specified here resolved to the public IP of the server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### auth_map _module-reference_
|
||||||
|
Default: `identity`
|
||||||
|
|
||||||
|
Use the specified table to translate SASL usernames before passing it to the
|
||||||
|
authentication provider.
|
||||||
|
|
||||||
|
Before username is looked up, it is normalized using function defined by
|
||||||
|
`auth_map_normalize`.
|
||||||
|
|
||||||
|
Note that `auth_map` does not affect the storage account name used. You probably
|
||||||
|
should also use `storage_map` in IMAP config block to handle this.
|
||||||
|
|
||||||
|
This directive is useful if used authentication provider does not support
|
||||||
|
using emails as usernames but you still want users to have separate mailboxes
|
||||||
|
on separate domains. In this case, use it with `email_localpart` table:
|
||||||
|
|
||||||
|
```
|
||||||
|
auth_map email_localpart
|
||||||
|
```
|
||||||
|
|
||||||
|
With this configuration, `user@example.org` and `user@example.com` will use
|
||||||
|
`user` credentials when authenticating, but will access `user@example.org` and
|
||||||
|
`user@example.com` mailboxes correspondingly. If you want to also accept
|
||||||
|
`user` as a username, use `auth_map email_localpart_optional`.
|
||||||
|
|
||||||
|
If you want `user@example.org` and `user@example.com` to have the same mailbox,
|
||||||
|
also set `storage_map` in IMAP config block to use `email_localpart`
|
||||||
|
(or `email_localpart_optional` if you want to also accept just "user"):
|
||||||
|
|
||||||
|
```
|
||||||
|
storage_map email_localpart
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case you will need to create storage accounts without domain part in
|
||||||
|
the name:
|
||||||
|
|
||||||
|
```
|
||||||
|
maddy imap-acct create user # instead of user@example.org
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### auth_map_normalize _function_
|
||||||
|
Default: `auto`
|
||||||
|
|
||||||
|
Normalization function to apply to SASL usernames before mapping
|
||||||
|
them to storage accounts.
|
||||||
|
|
||||||
|
Available options:
|
||||||
|
|
||||||
|
- `auto` `precis_casefold_email` for valid emails, `precis_casefold` otherwise.
|
||||||
|
- `precis_casefold_email` PRECIS UsernameCaseMapped profile + U-labels form for domain
|
||||||
|
- `precis_casefold` PRECIS UsernameCaseMapped profile for the entire string
|
||||||
|
- `precis_email` PRECIS UsernameCasePreserved profile + U-labels form for domain
|
||||||
|
- `precis` PRECIS UsernameCasePreserved profile for the entire string
|
||||||
|
- `casefold` Convert to lower case
|
||||||
|
- `noop` Nothing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### autogenerated_msg_domain _domain_
|
||||||
|
Default: not specified
|
||||||
|
|
||||||
|
Domain that is used in From field for auto-generated messages (such as Delivery
|
||||||
|
Status Notifications).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### tls `file` _cert-file_ _pkey-file_ | _module-reference_ | `off`
|
||||||
|
Default: not specified
|
||||||
|
|
||||||
|
Default TLS certificate to use for all endpoints.
|
||||||
|
|
||||||
|
Must be present in either all endpoint modules configuration blocks or as
|
||||||
|
global directive.
|
||||||
|
|
||||||
|
You can also specify other configuration options such as cipher suites and TLS
|
||||||
|
version. See maddy-tls(5) for details. maddy uses reasonable
|
||||||
|
cipher suites and TLS versions by default so you generally don't have to worry
|
||||||
|
about it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### tls_client { ... }
|
||||||
|
Default: not specified
|
||||||
|
|
||||||
|
This is optional block that specifies various TLS-related options to use when
|
||||||
|
making outbound connections. See TLS client configuration for details on
|
||||||
|
directives that can be used in it. maddy uses reasonable cipher suites and TLS
|
||||||
|
versions by default so you generally don't have to worry about it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### log _targets..._ | `off`
|
||||||
|
Default: `stderr`
|
||||||
|
|
||||||
|
Write log to one of more "targets".
|
||||||
|
|
||||||
|
The target can be one or the following:
|
||||||
|
|
||||||
|
- `stderr` – Write logs to stderr.
|
||||||
|
- `stderr_ts` – Write logs to stderr with timestamps.
|
||||||
|
- `syslog` – Send logs to the local syslog daemon.
|
||||||
|
- _file path_ – Write (append) logs to file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
log syslog /var/log/maddy.log
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Maddy does not perform log files rotation, this is the job of the
|
||||||
|
logrotate daemon. Send SIGUSR1 to maddy process to make it reopen log files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Enable verbose logging for all modules. You don't need that unless you are
|
||||||
|
reporting a bug.
|
||||||
|
|
225
docs/reference/modifiers/dkim.md
Normal file
225
docs/reference/modifiers/dkim.md
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
# DKIM signing
|
||||||
|
|
||||||
|
modify.dkim module is a modifier that signs messages using DKIM
|
||||||
|
protocol (RFC 6376).
|
||||||
|
|
||||||
|
Each configuration block specifies a single selector
|
||||||
|
and one or more domains.
|
||||||
|
|
||||||
|
A key will be generated or read for each domain, the key to use
|
||||||
|
for each message will be selected based on the SMTP envelope sender. Exception
|
||||||
|
for that is that for domain-less postmaster address and null address, the
|
||||||
|
key for the first domain will be used. If domain in envelope sender
|
||||||
|
does not match any of loaded keys, message will not be signed.
|
||||||
|
Additionally, for each messages From header is checked to
|
||||||
|
match MAIL FROM and authorization identity (username sender is logged in as).
|
||||||
|
This can be controlled using require_sender_match directive.
|
||||||
|
|
||||||
|
Generated private keys are stored in unencrypted PKCS#8 format
|
||||||
|
in state_directory/dkim_keys (`/var/lib/maddy/dkim_keys`).
|
||||||
|
In the same directory .dns files are generated that contain
|
||||||
|
public key for each domain formatted in the form of a DNS record.
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
domains and selector can be specified in arguments, so actual modify.dkim use can
|
||||||
|
be shortened to the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
modify {
|
||||||
|
dkim example.org selector
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration directives
|
||||||
|
|
||||||
|
```
|
||||||
|
modify.dkim {
|
||||||
|
debug no
|
||||||
|
domains example.org example.com
|
||||||
|
selector default
|
||||||
|
key_path dkim-keys/{domain}-{selector}.key
|
||||||
|
oversign_fields ...
|
||||||
|
sign_fields ...
|
||||||
|
header_canon relaxed
|
||||||
|
body_canon relaxed
|
||||||
|
sig_expiry 120h # 5 days
|
||||||
|
hash sha256
|
||||||
|
newkey_algo rsa2048
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### debug _boolean_
|
||||||
|
Default: global directive value
|
||||||
|
|
||||||
|
Enable verbose logging.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### domains _string-list_
|
||||||
|
**Required**. <br>
|
||||||
|
Default: not specified
|
||||||
|
|
||||||
|
|
||||||
|
ADministrative Management Domains (ADMDs) taking responsibility for messages.
|
||||||
|
|
||||||
|
Should be specified either as a directive or as an argument.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### selector _string_
|
||||||
|
**Required**. <br>
|
||||||
|
Default: not specified
|
||||||
|
|
||||||
|
Identifier of used key within the ADMD.
|
||||||
|
Should be specified either as a directive or as an argument.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### key_path _string_
|
||||||
|
Default: `dkim_keys/{domain}_{selector}.key`
|
||||||
|
|
||||||
|
Path to private key. It should be in PKCS#8 format wrapped in PAM encoding.
|
||||||
|
If key does not exist, it will be generated using algorithm specified
|
||||||
|
in newkey_algo.
|
||||||
|
|
||||||
|
Placeholders '{domain}' and '{selector}' will be replaced with corresponding
|
||||||
|
values from domain and selector directives.
|
||||||
|
|
||||||
|
Additionally, keys in PKCS#1 ("RSA PRIVATE KEY") and
|
||||||
|
RFC 5915 ("EC PRIVATE KEY") can be read by modify.dkim. Note, however that
|
||||||
|
newly generated keys are always in PKCS#8.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### oversign_fields _list..._
|
||||||
|
Default: see below
|
||||||
|
|
||||||
|
Header fields that should be signed n+1 times where n is times they are
|
||||||
|
present in the message. This makes it impossible to replace field
|
||||||
|
value by prepending another field with the same name to the message.
|
||||||
|
|
||||||
|
Fields specified here don't have to be also specified in `sign_fields`.
|
||||||
|
|
||||||
|
Default set of oversigned fields:
|
||||||
|
|
||||||
|
- Subject
|
||||||
|
- To
|
||||||
|
- From
|
||||||
|
- Date
|
||||||
|
- MIME-Version
|
||||||
|
- Content-Type
|
||||||
|
- Content-Transfer-Encoding
|
||||||
|
- Reply-To
|
||||||
|
- Message-Id
|
||||||
|
- References
|
||||||
|
- Autocrypt
|
||||||
|
- Openpgp
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### sign_fields _list..._
|
||||||
|
Default: see below
|
||||||
|
|
||||||
|
Header fields that should be signed n times where n is times they are
|
||||||
|
present in the message. For these fields, additional values can be prepended
|
||||||
|
by intermediate relays, but existing values can't be changed.
|
||||||
|
|
||||||
|
Default set of signed fields:
|
||||||
|
|
||||||
|
- List-Id
|
||||||
|
- List-Help
|
||||||
|
- List-Unsubscribe
|
||||||
|
- List-Post
|
||||||
|
- List-Owner
|
||||||
|
- List-Archive
|
||||||
|
- Resent-To
|
||||||
|
- Resent-Sender
|
||||||
|
- Resent-Message-Id
|
||||||
|
- Resent-Date
|
||||||
|
- Resent-From
|
||||||
|
- Resent-Cc
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### header_canon `relaxed` | `simple`
|
||||||
|
Default: `relaxed`
|
||||||
|
|
||||||
|
Canonicalization algorithm to use for header fields. With `relaxed`, whitespace within
|
||||||
|
fields can be modified without breaking the signature, with `simple` no
|
||||||
|
modifications are allowed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### body_canon `relaxed` | `simple`
|
||||||
|
Default: `relaxed`
|
||||||
|
|
||||||
|
Canonicalization algorithm to use for message body. With `relaxed`, whitespace within
|
||||||
|
can be modified without breaking the signature, with `simple` no
|
||||||
|
modifications are allowed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### sig_expiry _duration_
|
||||||
|
Default: `120h`
|
||||||
|
|
||||||
|
Time for which signature should be considered valid. Mainly used to prevent
|
||||||
|
unauthorized resending of old messages.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### hash _hash_
|
||||||
|
Default: `sha256`
|
||||||
|
|
||||||
|
Hash algorithm to use when computing body hash.
|
||||||
|
|
||||||
|
sha256 is the only supported algorithm now.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### newkey_algo `rsa4096` | `rsa2048` | `ed25519`
|
||||||
|
Default: `rsa2048`
|
||||||
|
|
||||||
|
Algorithm to use when generating a new key.
|
||||||
|
|
||||||
|
Currently ed25519 is **not** supported by most platforms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### require_sender_match _ids..._
|
||||||
|
Default: `envelope auth`
|
||||||
|
|
||||||
|
Require specified identifiers to match From header field and key domain,
|
||||||
|
otherwise - don't sign the message.
|
||||||
|
|
||||||
|
If From field contains multiple addresses, message will not be
|
||||||
|
signed unless `allow_multiple_from` is also specified. In that
|
||||||
|
case only first address will be compared.
|
||||||
|
|
||||||
|
Matching is done in a case-insensitive way.
|
||||||
|
|
||||||
|
Valid values:
|
||||||
|
|
||||||
|
- `off` – Disable check, always sign.
|
||||||
|
- `envelope` – Require MAIL FROM address to match From header.
|
||||||
|
- `auth` – If authorization identity contains @ - then require it to
|
||||||
|
fully match From header. Otherwise, check only local-part
|
||||||
|
(username).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### allow_multiple_from _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Allow multiple addresses in From header field for purposes of
|
||||||
|
`require_sender_match` checks. Only first address will be checked, however.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### sign_subdomains _boolean_
|
||||||
|
Default: `no`
|
||||||
|
|
||||||
|
Sign emails from subdomains using a top domain key.
|
||||||
|
|
||||||
|
Allows only one domain to be specified (can be worked around by using `modify.dkim`
|
||||||
|
multiple times).
|
63
docs/reference/modifiers/envelope.md
Normal file
63
docs/reference/modifiers/envelope.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# Envelope sender / recipient rewriting
|
||||||
|
|
||||||
|
`replace_sender` and `replace_rcpt` modules replace SMTP envelope addresses
|
||||||
|
based on the mapping defined by the table module (maddy-tables(5)). It is possible
|
||||||
|
to specify 1:N mappings. This allows, for example, implementing mailing lists.
|
||||||
|
|
||||||
|
The address is normalized before lookup (Punycode in domain-part is decoded,
|
||||||
|
Unicode is normalized to NFC, the whole string is case-folded).
|
||||||
|
|
||||||
|
First, the whole address is looked up. If there is no replacement, local-part
|
||||||
|
of the address is looked up separately and is replaced in the address while
|
||||||
|
keeping the domain part intact. Replacements are not applied recursively, that
|
||||||
|
is, lookup is not repeated for the replacement.
|
||||||
|
|
||||||
|
Recipients are not deduplicated after expansion, so message may be delivered
|
||||||
|
multiple times to a single recipient. However, used delivery target can apply
|
||||||
|
such deduplication (imapsql storage does it).
|
||||||
|
|
||||||
|
Definition:
|
||||||
|
|
||||||
|
```
|
||||||
|
replace_rcpt <table> [table arguments] {
|
||||||
|
[extended table config]
|
||||||
|
}
|
||||||
|
replace_sender <table> [table arguments] {
|
||||||
|
[extended table config]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
modify {
|
||||||
|
replace_rcpt file /etc/maddy/aliases
|
||||||
|
replace_rcpt static {
|
||||||
|
entry a@example.org b@example.org
|
||||||
|
entry c@example.org c1@example.org c2@example.org
|
||||||
|
}
|
||||||
|
replace_rcpt regexp "(.+)@example.net" "$1@example.org"
|
||||||
|
replace_rcpt regexp "(.+)@example.net" "$1@example.org" "$1@example.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Possible contents of /etc/maddy/aliases in the example above:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Replace 'cat' with any domain to 'dog'.
|
||||||
|
# E.g. cat@example.net -> dog@example.net
|
||||||
|
cat: dog
|
||||||
|
|
||||||
|
# Replace cat@example.org with cat@example.com.
|
||||||
|
# Takes priority over the previous line.
|
||||||
|
cat@example.org: cat@example.com
|
||||||
|
|
||||||
|
# Using aliases in multiple lines
|
||||||
|
cat2: dog
|
||||||
|
cat2: mouse
|
||||||
|
cat2@example.org: cat@example.com
|
||||||
|
cat2@example.org: cat@example.net
|
||||||
|
# Comma-separated aliases in multiple lines
|
||||||
|
cat3: dog , mouse
|
||||||
|
cat3@example.org: cat@example.com , cat@example.net
|
||||||
|
```
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user