Merge pull request #1601 from aendeavor/master

- outsourced badges-links in `README.md` to clean this section up
- refactored `.travis.yml` with Prettier and added **shellcheck capabilities**
- refactored Makefile to make it verbose where it should be and added a **shellcheck target**
- refactored all scripts in `target/` (except `start-mailserver.sh`)
- added **coding style guidelines** to `CONTRIBUTING.md` so everyone can and must adhere to consistent guidelines
- `start-mailserver.sh` had to be touched due to renaming of functions
- added a table of contents to Markdown documents
This commit is contained in:
Georg Lauterbach 2020-09-09 18:31:42 +02:00 committed by GitHub
commit ae6f41e303
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1006 additions and 689 deletions

View file

@ -1,23 +1,41 @@
branches: branches:
except: except:
- donttestme - donttestme
language: bash language: bash
sudo: required sudo: required
env: env:
global: global:
- HADOLINT_VERSION=1.17.1 - HADOLINT_VERSION=1.17.1
- SHELLCHECK_VERSION=0.7.1
addons:
apt:
packages:
- xz-utils
services: services:
- docker - docker
before_install: before_install:
- sudo curl -L https://github.com/hadolint/hadolint/releases/download/v$HADOLINT_VERSION/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint - sudo curl -L https://github.com/hadolint/hadolint/releases/download/v$HADOLINT_VERSION/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint
- sudo chmod +rx /usr/local/bin/hadolint - sudo chmod +rx /usr/local/bin/hadolint
- sudo wget -qO- "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar -xJv
- sudo cp "shellcheck-v${SHELLCHECK_VERSION}/shellcheck" /usr/bin/
install: install:
- make lint - make lint
- travis_retry travis_wait make build - travis_retry travis_wait make build
script: script:
- make generate-accounts run generate-accounts-after-run fixtures tests - make shellcheck
- make generate-accounts run generate-accounts-after-run fixtures tests
after_script: after_script:
- make clean - make clean
notifications: notifications:
slack: slack:
secure: TTo1z9nbZCWcIdfPwypubNa3y+pwvfgDGlzEVAGEuK7uuIpmEoAcAUNSSPTnbewDGHnDl8t/ml93MtvP+a+IVuAKytMqF39PHyoZO7aUl9J62V+G75OmnyGjXGJm40pQosCS6LzqoRRYXotl9+fwH568Kf4ifXCrMZX1d+ir7Ww= secure: TTo1z9nbZCWcIdfPwypubNa3y+pwvfgDGlzEVAGEuK7uuIpmEoAcAUNSSPTnbewDGHnDl8t/ml93MtvP+a+IVuAKytMqF39PHyoZO7aUl9J62V+G75OmnyGjXGJm40pQosCS6LzqoRRYXotl9+fwH568Kf4ifXCrMZX1d+ir7Ww=

View file

@ -2,7 +2,7 @@
## 6.2.0+ ## 6.2.0+
see https://github.com/tomav/docker-mailserver/releases * see <https://github.com/tomav/docker-mailserver/releases>
## 6.1.0 ## 6.1.0
@ -56,93 +56,114 @@ see https://github.com/tomav/docker-mailserver/releases
Fixes to setup where made for deletion and addition. Fixes to setup where made for deletion and addition.
## 5.7.0 ## 5.7.0
* Delmailuser (#878) * Delmailuser (#878)
You can now delete users and the mailbox You can now delete users and the mailbox
* Backup config folder while testing (#901) * Backup config folder while testing (#901)
* added error messages to letsencrypt on startup (#898) * added error messages to letsencrypt on startup (#898)
## 5.6.1 ## 5.6.1
* Update docker-configomat (#680)
* Update docker-configomat (#680)
## 5.6.0 ## 5.6.0
* Generate SRS secret on first run and store it (#891) * Generate SRS secret on first run and store it (#891)
The secret will be constant afther this. The secret will be constant afther this.
## 5.5.0 ## 5.5.0
* Add /var/lib/dovecot to mailstate persistence (#887) * Add /var/lib/dovecot to mailstate persistence (#887)
## 5.4.0 ## 5.4.0
* Allow configuring SRS secrets using the environment (#885) * Allow configuring SRS secrets using the environment (#885)
You can set your own secret with the env SRS_SECRET You can set your own secret with the env SRS_SECRET
By default it uses the docker generated secret By default it uses the docker generated secret
* Removed unneeded check for Let's encrypt cert.pem (#843) * Removed unneeded check for Let's encrypt cert.pem (#843)
## 5.3.0 ## 5.3.0
* Added reject_authenticated_sender_login_mismatch (#872) * Added reject_authenticated_sender_login_mismatch (#872)
You can enable it with the env SPOOF_PROTECTION You can enable it with the env SPOOF_PROTECTION
It is not enabled by default It is not enabled by default
## 5.2.0 ## 5.2.0
* Setting quiet mode on invoke-rc.d (#792) * Setting quiet mode on invoke-rc.d (#792)
* Implement undef option for SA_SPAM_SUBJECT (#767) * Implement undef option for SA_SPAM_SUBJECT (#767)
## 5.1.0 ## 5.1.0
* Dkim key size can be changed (#868) * Dkim key size can be changed (#868)
It defaults to 2048 bits It defaults to 2048 bits
## 5.0.1 ## 5.0.1
* update postmaster_address in dovecot config according to * update postmaster_address in dovecot config according to
POSTMASTER_ADDRESS env var (#866) POSTMASTER_ADDRESS env var (#866)
## 5.0.0 ## 5.0.0
* Use Nist tls recommendations (#831) * Use Nist tls recommendations (#831)
This might break access with older email clients that use This might break access with older email clients that use
an older version of openssl. You can TLS_LEVEL to lower an older version of openssl. You can TLS_LEVEL to lower
the ciphers. the ciphers.
## 4.2.0 ## 4.2.0
* Add environment variable to allow for customizing postsrsd's
SRS_EXCLUDE_DOMAINS setting (#849, #842) * Add environment variable to allow for customizing postsrsd's SRS_EXCLUDE_DOMAINS setting (#849, #842)
## 4.1.0 ## 4.1.0
* fixed greedy postgrey sed command (#845) * fixed greedy postgrey sed command (#845)
* postscreen implementation altered (#846) * postscreen implementation altered (#846)
You can now apply sender and receives restrictions You can now apply sender and receives restrictions
## 4.0.0 ## 4.0.0
* moved fail2ban function from setup.sh to own file (#837) * moved fail2ban function from setup.sh to own file (#837)
This might break automatic scripting and you need to use This might break automatic scripting and you need to use
fail2ban now fail2ban now
## 3.4.0 ## 3.4.0
* Generate new DH param weekly instead of daily (#834, #836) * Generate new DH param weekly instead of daily (#834, #836)
## 3.3.1 ## 3.3.1
* added config-path option to setup.sh script (#698) * added config-path option to setup.sh script (#698)
## 3.3.0 ## 3.3.0
* Restrict access (#452, #816) * Restrict access (#452, #816)
## 3.2.3 ## 3.2.3
* Introduce .env for docker-compose examples (#815) * Introduce .env for docker-compose examples (#815)
## 3.2.2 ## 3.2.2
* Changed Junk folder to be created and subscribed by default (#806) * Changed Junk folder to be created and subscribed by default (#806)
## 3.2.1 (2018-02-06) ## 3.2.1 (2018-02-06)
* Added reject_sender_login_mismatch (#811) * Added reject_sender_login_mismatch (#811)
## 3.2.0 (2018-02-06) ## 3.2.0 (2018-02-06)
* Add SRS to fix SPF issues on redirect (#611, #814) * Add SRS to fix SPF issues on redirect (#611, #814)
## 3.1.0 (2018-02-04) ## 3.1.0 (2018-02-04)
* Introduced Postscreen * Introduced Postscreen
Breaks email submission on port 25. Sending emails should be done on port 465 or 587 Breaks email submission on port 25. Sending emails should be done on port 465 or 587
## 3.0.0 (2018-02-04) ## 3.0.0 (2018-02-04)
* Image rebased on Debian stable * Image rebased on Debian stable
## 2.0.0 (2016-05-09) ## 2.0.0 (2016-05-09)
* New version * New version
* Major redesign of configuration * Major redesign of configuration

View file

@ -2,18 +2,29 @@
`docker-mailserver` is OpenSource. That means that you can contribute on enhancements, bug fixing or improving the documentation in the Wiki. `docker-mailserver` is OpenSource. That means that you can contribute on enhancements, bug fixing or improving the documentation in the Wiki.
## Open an issue 1. [Issues & PRs](#issues--prs)
1. [Open an Issue](#open-an-issue)
2. [Pull Request](#pull-requests)
2. [Coding Style](#coding-style)
1. [Bash and Shell](#bash-and-shell)
2. [YAML](#yaml)
## Issues & PRs
### Open an issue
When opening an issue, please provide details use case to let the community reproduce your problem. When opening an issue, please provide details use case to let the community reproduce your problem.
Please start the mail server with env `DMS_DEBUG=1` and paste the ouput into the issue. Please start the mail server with env `DMS_DEBUG=1` and paste the output into the issue.
## Pull Requests ### Pull Requests
#### Project architecture #### Project architecture
├── config # User: personal configurations ``` TXT
├── target # Developer: default server configuration, used when building the image ├── config # User: personal configurations
└── test # Developer: integration tests to check that everything keeps working ├── target # Developer: default server configuration, used when building the image
└── test # Developer: integration tests to check that everything keeps working
```
#### Submit a Pull-Request #### Submit a Pull-Request
@ -29,9 +40,158 @@ The development workflow is the following:
- Use `make clean all` to build image locally and run tests - Use `make clean all` to build image locally and run tests
Note that tests work on Linux only; they hang on Mac and Windows. Note that tests work on Linux only; they hang on Mac and Windows.
- Document your improvements in `README.md` or Wiki depending on content - Document your improvements in `README.md` or Wiki depending on content
- [Commit](https://help.github.com/articles/closing-issues-via-commit-messages/), push and make a pull-request - [Commit][commit], if possible with [signing your commit with a GPG key][gpg], push and make a pull-request
- Pull-request is automatically tested on Travis - Pull-request is automatically tested on Travis
- When tests are green, a review may be done - When tests are green, a review may be done
- When changed are validated, your branch is merged into `master` - When changed are validated, your branch is merged into `master`
- `master` is automatically tested on Travis - `master` is automatically tested on Travis
- Docker builds a new `latest` image - Docker builds a new `latest` image
## Coding Style
### Bash and Shell
When refactoring, writing or altering Script, that is Shell and Bash scripts, in any way, adhere to these rules:
1. **Adjust your style of coding to the style that is already present**! Even if you do not like it, this is due to consistency. Look up the GNU coding style guide. There was a lot of work involved in making these scripts consistent.
2. **Use `shellcheck` to check your scripts**! Your contributions are checked by TravisCI with shellcheck.
3. There is a **`.editorconfig`** file. Make your IDE use it or adhere to it manually!
4. It's okay to use `/bin/bash` instead of `/bin/sh`. You can alternatively use `/usr/bin/env bash`.
5. `setup.sh` provides a good starting point to look for.
6. When appropriate, use the `set` builtin. We recommend `set -euEo pipefail` (very strong) or `set -uE` (weaker).
#### Styling rules
##### Initial Description
When writing a script, provide the version and the script's task. We use [semantic versioning][semver] - so do you.
``` BASH
#!/usr/bin/env bash
# version 0.1.0
#
# <TASK DESCRIPTION> -> cut this off
# to make it not longer than approx.
# 80 cols.
```
##### If-Else-Statements
``` BASH
# when using braces, use double braces
# remember you do not need "" when using [[ ]]
if [[ <CONDITION1> ]] && [[ -f ${FILE} ]]
then
<CODE TO RUN>
# when running code, you don't need them
elif <COMMAND TO RUN>
<CODE TO TUN>
else
<CODE TO TUN>
fi
# equality checks with numbers, use
# -eq/-ne/-lt/-ge, not != or ==
if [[ $VAR -ne 42 ]] || [[ $SOME_VAR -eq 6 ]]
then
<CODE TO RUN>
fi
```
##### Variables & Braces
Variables are always uppercase. We always use braces. If you forgot this and want to change it later, you can use [this link][regex], which points to <https://regex101.com>. The used regex is `\$([^{("\\'\/])([a-zA-Z0-9_]*)([^}\/ \t'"\n.\]:]*)`, where you should in practice be able to replace all variable occurrences without braces with occurrences with braces.
``` BASH
# good
local VAR="good"
local NEW="${VAR}"
# bad
var="bad"
```
##### Loops
Like `if-else`, loops look like this
``` BASH
for / while <LOOP CONDITION>
do
<CODE TO RUN>
done
```
##### Functions
It's always nice to see the use of functions. Not only as it's more C-style, but it also provides a clear structure. If scripts are small, this is unnecessary, but if they become larger, please consider using functions. When doing so, provide `function _main()`. When using functions, they are **always** at the top of the script!
``` BASH
function _<name_underscored_and_lowercase>()
{
<CODE TO RUN>
# variables that can be local should be local
local _<LOCAL_VARIABLE_NAME>
}
```
##### Error Tracing
A construct to trace error in your scripts looks like this. Please use it like this (copy-paste) to make errors streamlined. Remember: Remove `set -x` in the end. This of debugging purposes only.
``` BASH
set -euxEo pipefail
trap '_report_err $_ $LINENO $?' ERR
function _report_err()
{
echo "ERROR occurred :: source (hint) $1 ; line $2 ; exit code $3 ;;" >&2
<CODE TO RUN AFTERWARDS>
}
```
##### Comments and Descriptiveness
Comments should only describe non-obvious matters. Comments should start lowercase when they aren't sentences. Make the code **self-descriptive** by using meaningful names! Make comments not longer than approximately 80 columns, then wrap the line.
A negative example:
``` BASH
# adds one to the first argument and print it to stdout
function _add_one()
{
# save the first variable
local FIRST=$1
# add one here
local RESULT=$(( _FIRST + 1 ))
# print it to stdout
echo "$_RESULT"
}
```
A positive example:
``` BASH
# writes result to stdout
function _add_one()
{
echo $(( $1 + 1 ))
}
```
### YAML
When formatting YAML files, you can opt for [Prettier][prettier]. There are any plugins for IDEs around.
[//]: # (Links)
[commit]: https://help.github.com/articles/closing-issues-via-commit-messages/
[gpg]: https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key
[semver]: https://semver.org/
[regex]: https://regex101.com/r/ikzJpF/4
[prettier]: https://prettier.io

View file

@ -182,8 +182,7 @@ COPY target/fail2ban/filter.d/postfix-sasl.conf /etc/fail2ban/filter.d/postfix-s
RUN mkdir /var/run/fail2ban RUN mkdir /var/run/fail2ban
# Enables Pyzor and Razor # Enables Pyzor and Razor
RUN su - amavis -c "razor-admin -create && \ RUN su - amavis -c "razor-admin -create && razor-admin -register"
razor-admin -register"
# Configure DKIM (opendkim) # Configure DKIM (opendkim)
# DKIM config files # DKIM config files

View file

@ -1,9 +1,14 @@
SHELL = /bin/bash
NAME = tvial/docker-mailserver:testing NAME = tvial/docker-mailserver:testing
VCS_REF := $(shell git rev-parse --short HEAD) VCS_REF := $(shell git rev-parse --short HEAD)
VCS_VERSION := $(shell git describe --tags --contains --always) VCS_VERSION := $(shell git describe --tags --contains --always)
SLEEP = 15s
all: build backup generate-accounts run generate-accounts-after-run fixtures tests clean all: build backup generate-accounts run generate-accounts-after-run fixtures tests clean
no-build: backup generate-accounts run generate-accounts-after-run fixtures tests clean no-build: backup generate-accounts run generate-accounts-after-run fixtures tests clean
complete_test: lint build generate-accounts run generate-accounts-after-run fixtures tests
build: build:
docker build \ docker build \
@ -12,22 +17,24 @@ build:
-t $(NAME) . -t $(NAME) .
backup: backup:
# if backup directories exist, clean hasn't been called, therefore we shouldn't overwrite it. It still contains the original content. # if backup directories exist, clean hasn't been called, therefore
@if [ ! -d config.bak ]; then\ # we shouldn't overwrite it. It still contains the original content.
cp -rp config config.bak; \ @ if [ ! -d config.bak ]; then\
cp -rp config config.bak;\
fi fi
@if [ ! -d testconfig.bak ]; then\ @ if [ ! -d testconfig.bak ]; then\
cp -rp test/config testconfig.bak ;\ cp -rp test/config testconfig.bak;\
fi fi
generate-accounts: generate-accounts:
docker run --rm -e MAIL_USER=user1@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' > test/config/postfix-accounts.cf @ docker run --rm -e MAIL_USER=user1@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' > test/config/postfix-accounts.cf
docker run --rm -e MAIL_USER=user2@otherdomain.tld -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf @ docker run --rm -e MAIL_USER=user2@otherdomain.tld -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf
echo "# this is a test comment, please don't delete me :'(" >> test/config/postfix-accounts.cf @ echo "# this is a test comment, please don't delete me :'(" >> test/config/postfix-accounts.cf
echo " # this is also a test comment, :O" >> test/config/postfix-accounts.cf @ echo " # this is also a test comment, :O" >> test/config/postfix-accounts.cf
run: run:
# Run containers # run containers
-@ echo "Sleeping $(SLEEP) after each container"
docker run --rm -d --name mail \ docker run --rm -d --name mail \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -50,14 +57,14 @@ run:
-e PERMIT_DOCKER=host \ -e PERMIT_DOCKER=host \
-e DMS_DEBUG=0 \ -e DMS_DEBUG=0 \
-h mail.my-domain.com -t $(NAME) -h mail.my-domain.com -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_smtponly_without_config \ docker run --rm -d --name mail_smtponly_without_config \
-e SMTP_ONLY=1 \ -e SMTP_ONLY=1 \
-e ENABLE_LDAP=1 \ -e ENABLE_LDAP=1 \
-e PERMIT_DOCKER=network \ -e PERMIT_DOCKER=network \
-e OVERRIDE_HOSTNAME=mail.mydomain.com \ -e OVERRIDE_HOSTNAME=mail.mydomain.com \
-t $(NAME) -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_override_hostname \ docker run --rm -d --name mail_override_hostname \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -67,7 +74,7 @@ run:
-e OVERRIDE_HOSTNAME=mail.my-domain.com \ -e OVERRIDE_HOSTNAME=mail.my-domain.com \
-h unknown.domain.tld \ -h unknown.domain.tld \
-t $(NAME) -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_domainname \ docker run --rm -d --name mail_domainname \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -77,7 +84,7 @@ run:
-e DOMAINNAME=my-domain.com \ -e DOMAINNAME=my-domain.com \
-h unknown.domain.tld \ -h unknown.domain.tld \
-t $(NAME) -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_srs_domainname \ docker run --rm -d --name mail_srs_domainname \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -88,7 +95,7 @@ run:
-e DOMAINNAME=my-domain.com \ -e DOMAINNAME=my-domain.com \
-h unknown.domain.tld \ -h unknown.domain.tld \
-t $(NAME) -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_disabled_clamav_spamassassin \ docker run --rm -d --name mail_disabled_clamav_spamassassin \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -96,19 +103,19 @@ run:
-e ENABLE_SPAMASSASSIN=0 \ -e ENABLE_SPAMASSASSIN=0 \
-e DMS_DEBUG=0 \ -e DMS_DEBUG=0 \
-h mail.my-domain.com -t $(NAME) -h mail.my-domain.com -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
generate-accounts-after-run: generate-accounts-after-run:
docker run --rm -e MAIL_USER=added@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf @ docker run --rm -e MAIL_USER=added@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf
docker exec mail addmailuser pass@localhost.localdomain 'may be \a `p^a.*ssword' @ docker exec mail addmailuser pass@localhost.localdomain 'may be \a `p^a.*ssword'
@ sleep $(SLEEP)
sleep 10
fixtures: fixtures:
# Setup sieve # setup sieve
docker cp "`pwd`/test/config/sieve/dovecot.sieve" mail:/var/mail/localhost.localdomain/user1/.dovecot.sieve docker cp "`pwd`/test/config/sieve/dovecot.sieve" mail:/var/mail/localhost.localdomain/user1/.dovecot.sieve
sleep 30 sleep $(SLEEP)
# Sending test mails sleep $(SLEEP)
# sending test mails
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt" docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-virus.txt" docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-virus.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-alias-external.txt" docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-alias-external.txt"
@ -126,35 +133,45 @@ fixtures:
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/non-existing-user.txt" docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/non-existing-user.txt"
docker exec mail_disabled_clamav_spamassassin /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt" docker exec mail_disabled_clamav_spamassassin /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
docker exec mail /bin/sh -c "sendmail root < /tmp/docker-mailserver-test/email-templates/root-email.txt" docker exec mail /bin/sh -c "sendmail root < /tmp/docker-mailserver-test/email-templates/root-email.txt"
# postfix virtual transport lmtp # postfix virtual transport lmtp
docker exec mail_override_hostname /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt" docker exec mail_override_hostname /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
# Wait for mails to be analyzed # wait for mails to be analyzed
sleep 80 sleep 80
tests: tests:
# Start tests
./test/bats/bin/bats test/*.bats ./test/bats/bin/bats test/*.bats
.PHONY: ALWAYS_RUN .PHONY: ALWAYS_RUN
test/%.bats: ALWAYS_RUN test/%.bats: ALWAYS_RUN
./test/bats/bin/bats $@ ./test/bats/bin/bats $@
lint: lint:
# List files which name starts with 'Dockerfile' # List files which name starts with 'Dockerfile'
# eg. Dockerfile, Dockerfile.build, etc. # eg. Dockerfile, Dockerfile.build, etc.
git ls-files --exclude='Dockerfile*' --ignored | xargs --max-lines=1 hadolint -@ git ls-files --exclude='Dockerfile*' --ignored | xargs --max-lines=1 hadolint
clean: clean:
# Remove running and stopped test containers # remove running and stopped test containers
-docker ps -a | grep -E "docker-mailserver:testing|ldap_for_mail" | cut -f 1-1 -d ' ' | xargs --no-run-if-empty docker rm -f -@ docker ps -a | grep -E "docker-mailserver:testing|ldap_for_mail" | cut -f 1-1 -d ' ' | xargs --no-run-if-empty docker rm -f
-@ if [ -d config.bak ]; then\
@if [ -d config.bak ]; then\
rm -rf config ;\ rm -rf config ;\
mv config.bak config ;\ mv config.bak config ;\
fi fi
@if [ -d testconfig.bak ]; then\ -@ if [ -d testconfig.bak ]; then\
sudo rm -rf test/config ;\ sudo rm -rf test/config ;\
mv testconfig.bak test/config ;\ mv testconfig.bak test/config ;\
fi fi
-sudo rm -rf test/onedir test/alias test/quota test/relay test/config/dovecot-lmtp/userdb test/config/key* test/config/opendkim/keys/domain.tld/ test/config/opendkim/keys/example.com/ test/config/opendkim/keys/localdomain2.com/ test/config/postfix-aliases.cf test/config/postfix-receive-access.cf test/config/postfix-receive-access.cfe test/config/dovecot-quotas.cf test/config/postfix-send-access.cf test/config/postfix-send-access.cfe test/config/relay-hosts/chksum test/config/relay-hosts/postfix-aliases.cf test/config/dhparams.pem -@ sudo rm -rf test/onedir test/alias test/quota test/relay test/config/dovecot-lmtp/userdb test/config/key* test/config/opendkim/keys/domain.tld/ test/config/opendkim/keys/example.com/ test/config/opendkim/keys/localdomain2.com/ test/config/postfix-aliases.cf test/config/postfix-receive-access.cf test/config/postfix-receive-access.cfe test/config/dovecot-quotas.cf test/config/postfix-send-access.cf test/config/postfix-send-access.cfe test/config/relay-hosts/chksum test/config/relay-hosts/postfix-aliases.cf test/config/dhparams.pem test/config/dovecot-lmtp/dh.pem test/config/relay-hosts/dovecot-quotas.cf test/config/user-patches.sh test/alias/config/postfix-virtual.cf test/quota/config/dovecot-quotas.cf test/quota/config/postfix-accounts.cf test/relay/config/postfix-relaymap.cf test/relay/config/postfix-sasl-password.cf
shellcheck:
@ echo -e "Testing shell / bash scripts with shellcheck\n"
@ /usr/bin/shellcheck --version
@ echo ''
# currently without `start-mailserver` as this is to be merged separately
@ if find -iname "*.sh" -not -path "./test/*" -not -path "./target/docker-configomat/*" -not -wholename ./target/start-mailserver.sh -exec /usr/bin/shellcheck -S style -Cauto -o all -e SC2250,SC2154 -W 50 {} \; | grep .; then\
echo -e "\nError" ;\
exit 1 ;\
else\
echo -e '\nSuccess' ;\
fi

View file

@ -1,12 +1,35 @@
# docker-mailserver # docker-mailserver
[![Build Status](https://travis-ci.org/tomav/docker-mailserver.svg?branch=master)](https://travis-ci.org/tomav/docker-mailserver) [![Docker Pulls](https://img.shields.io/docker/pulls/tvial/docker-mailserver.svg)](https://hub.docker.com/r/tvial/docker-mailserver/) [![Docker layers](https://images.microbadger.com/badges/image/tvial/docker-mailserver.svg)](https://microbadger.com/images/tvial/docker-mailserver) [![Github Stars](https://img.shields.io/github/stars/tomav/docker-mailserver.svg?label=github%20%E2%98%85)](https://github.com/tomav/docker-mailserver/) [![Github Stars](https://img.shields.io/github/contributors/tomav/docker-mailserver.svg)](https://github.com/tomav/docker-mailserver/) [![Github Forks](https://img.shields.io/github/forks/tomav/docker-mailserver.svg?label=github%20forks)](https://github.com/tomav/docker-mailserver/) [![Gitter](https://img.shields.io/gitter/room/tomav/docker-mailserver.svg)](https://gitter.im/tomav/docker-mailserver) [![Build Status][build_status]][build_status::travis] [![Docker Pulls][docker_pulls]][docker_hub_pulls::hub] [![Docker layers][layers]][layers_outer::badger] [![Github Stars][gh_stars]][repo] [![Contributors][contributors]][repo] [![Github Forks][forks]][repo] [![Gitter][shields::gitter]][gitter]
[build_status]: https://travis-ci.org/tomav/docker-mailserver.svg?branch=master
[build_status::travis]: https://travis-ci.org/tomav/docker-mailserver
[docker_pulls]: https://img.shields.io/docker/pulls/tvial/docker-mailserver.svg
[docker_hub_pulls::hub]: https://hub.docker.com/r/tvial/docker-mailserver/
[layers]: https://images.microbadger.com/badges/image/tvial/docker-mailserver.svg
[layers_outer::badger]: https://microbadger.com/images/tvial/docker-mailserver
[gh_stars]: https://img.shields.io/github/stars/tomav/docker-mailserver.svg?label=github%20%E2%98%85
[repo]: https://github.com/tomav/docker-mailserver/
[contributors]: https://img.shields.io/github/contributors/tomav/docker-mailserver.svg
[forks]: https://img.shields.io/github/forks/tomav/docker-mailserver.svg?label=github%20forks
[shields::gitter]: https://img.shields.io/gitter/room/tomav/docker-mailserver.svg
[gitter]: https://gitter.im/tomav/docker-mailserver
A fullstack but simple mail server (smtp, imap, antispam, antivirus...). A fullstack but simple mail server (smtp, imap, antispam, antivirus...).
Only configuration files, no SQL database. Keep it simple and versioned. Only configuration files, no SQL database. Keep it simple and versioned.
Easy to deploy and upgrade. Easy to deploy and upgrade.
## ANNOUNCEMENT Why I created this image: [Simple Mail Server with Docker](http://tvi.al/simple-mail-server-with-docker/)
1. [Announcement](#announcement)
2. [Includes](#includes)
3. [Issues & Contributing](#issues--contributing)
4. [Requirements](#requirements)
5. [Usage](#usage)
6. [Examples](#examples)
7. [Environment Variables](#environment-variables)
## Announcement
At this point we have merged the next branch based on Debian Buster into master. At this point we have merged the next branch based on Debian Buster into master.
That means the docker image latest uses Buster. The change may break things! That means the docker image latest uses Buster. The change may break things!
@ -19,7 +42,7 @@ The following possibly breaking changes are known:
If you want to stick to the old version a while longer, either switch to stable or to a specific version. If you want to stick to the old version a while longer, either switch to stable or to a specific version.
If you run into problems, please raise issues and ask for help. Don't forget to provide details. If you run into problems, please raise issues and ask for help. Don't forget to provide details.
Includes: ## Includes
- [Postfix](http://www.postfix.org) with smtp or ldap auth - [Postfix](http://www.postfix.org) with smtp or ldap auth
- [Dovecot](https://www.dovecot.org) for sasl, imap (and optional pop3) with ssl support, with ldap auth, sieve and [quotas](https://github.com/tomav/docker-mailserver/wiki/Configure-Accounts#mailbox-quota) - [Dovecot](https://www.dovecot.org) for sasl, imap (and optional pop3) with ssl support, with ldap auth, sieve and [quotas](https://github.com/tomav/docker-mailserver/wiki/Configure-Accounts#mailbox-quota)
@ -42,9 +65,9 @@ Includes:
- Plus addressing (a.k.a. [extension delimiters](http://www.postfix.org/postconf.5.html#recipient_delimiter)) - Plus addressing (a.k.a. [extension delimiters](http://www.postfix.org/postconf.5.html#recipient_delimiter))
works out of the box: email for `you+extension@example.com` go to `you@example.com` works out of the box: email for `you+extension@example.com` go to `you@example.com`
Why I created this image: [Simple mail server with Docker](http://tvi.al/simple-mail-server-with-docker/) ## Issues & Contributing
Before you open an issue, please have a look this `README`, the [Wiki](https://github.com/tomav/docker-mailserver/wiki/) and Postfix/Dovecot documentation. Before you open an issue, please have a look this `README`, the [Wiki](https://github.com/tomav/docker-mailserver/wiki/) and Postfix/Dovecot documentation. If you'd like to contribute, read [`CONTRIBUTING.md`](./CONTRIBUTING.md) thoroughly.
## Requirements ## Requirements
@ -67,7 +90,7 @@ Minimum:
Download the docker-compose.yml, the .env and the setup.sh files: Download the docker-compose.yml, the .env and the setup.sh files:
``` SH ``` BASH
curl -o setup.sh https://raw.githubusercontent.com/tomav/docker-mailserver/master/setup.sh; chmod a+x ./setup.sh curl -o setup.sh https://raw.githubusercontent.com/tomav/docker-mailserver/master/setup.sh; chmod a+x ./setup.sh
curl -o docker-compose.yml https://raw.githubusercontent.com/tomav/docker-mailserver/master/docker-compose.yml.dist curl -o docker-compose.yml https://raw.githubusercontent.com/tomav/docker-mailserver/master/docker-compose.yml.dist
@ -89,7 +112,7 @@ curl -o env-mailserver https://raw.githubusercontent.com/tomav/docker-mailserver
**Note:** If you want to use a bare domain (host name equals domain name) see [FAQ](https://github.com/tomav/docker-mailserver/wiki/FAQ-and-Tips#can-i-use-nakedbare-domains-no-host-name). **Note:** If you want to use a bare domain (host name equals domain name) see [FAQ](https://github.com/tomav/docker-mailserver/wiki/FAQ-and-Tips#can-i-use-nakedbare-domains-no-host-name).
### Starting the Container ### Start the Container
``` BASH ``` BASH
docker-compose up -d mail docker-compose up -d mail
@ -129,8 +152,6 @@ If you got any problems with SPF and/or forwarding mails, give [SRS](https://git
#### For informational purposes #### For informational purposes
Your config folder will be mounted in `/tmp/docker-mailserver/`. To understand how things work on boot, please have a look at [start-mailserver.sh](https://github.com/tomav/docker-mailserver/blob/master/target/start-mailserver.sh)
`restart: always` ensures that the mail server container (and Filebeat/ELK containers when using the mail server together with ELK stack) is automatically restarted by Docker in cases like a Docker service or host restart or container exit. `restart: always` ensures that the mail server container (and Filebeat/ELK containers when using the mail server together with ELK stack) is automatically restarted by Docker in cases like a Docker service or host restart or container exit.
#### Exposed ports #### Exposed ports
@ -148,12 +169,12 @@ Your config folder will be mounted in `/tmp/docker-mailserver/`. To understand h
See the [wiki](https://github.com/tomav/docker-mailserver/wiki) for further details and best practice advice, especially regarding security concerns. See the [wiki](https://github.com/tomav/docker-mailserver/wiki) for further details and best practice advice, especially regarding security concerns.
### Examples ## Examples
#### Just the relevant environmental variables ### With Relevant Environmental Variables
```yaml ``` YAML
version: '2' version: '3.8'
services: services:
mail: mail:
@ -195,7 +216,7 @@ volumes:
#### LDAP setup #### LDAP setup
``` YAML ``` YAML
version: '2' version: '3.8'
services: services:
mail: mail:
@ -256,9 +277,7 @@ volumes:
## Environment variables ## Environment variables
Please check [how the container starts](https://github.com/tomav/docker-mailserver/blob/master/target/start-mailserver.sh) to understand what's expected. Also if an option doesn't work as documented here, check if you are running the latest image! If an option doesn't work as documented here, check if you are running the latest image! Value in **bold** is the default value.
Value in **bold** is the default value.
### Assignments ### Assignments
@ -794,7 +813,7 @@ you to replace both instead of just the envelope sender.
#### Default Relay Host #### Default Relay Host
#### DEFAULT_RELAY_HOST ##### DEFAULT_RELAY_HOST
- **empty** => don't set default relayhost setting in main.cf - **empty** => don't set default relayhost setting in main.cf
- default host and port to relay all mail through. - default host and port to relay all mail through.
@ -803,22 +822,22 @@ you to replace both instead of just the envelope sender.
#### Multi-domain Relay Hosts #### Multi-domain Relay Hosts
#### RELAY_HOST ##### RELAY_HOST
- **empty** => don't configure relay host - **empty** => don't configure relay host
- default host to relay mail through - default host to relay mail through
#### RELAY_PORT ##### RELAY_PORT
- **empty** => 25 - **empty** => 25
- default port to relay mail through - default port to relay mail through
#### RELAY_USER ##### RELAY_USER
- **empty** => no default - **empty** => no default
- default relay username (if no specific entry exists in postfix-sasl-password.cf) - default relay username (if no specific entry exists in postfix-sasl-password.cf)
#### RELAY_PASSWORD ##### RELAY_PASSWORD
- **empty** => no default - **empty** => no default
- password for default relay user - password for default relay user

187
setup.sh
View file

@ -4,11 +4,11 @@
# included in the docker-mailserver # included in the docker-mailserver
set -euEo pipefail set -euEo pipefail
trap '_report_err $_ $LINENO $?' ERR trap '_report_err ${_:-"SOURCE UNKNOWN"} ${LINENO} ${?}' ERR
function _report_err() function _report_err()
{ {
echo "ERROR occured :: source (hint) $1 ; line $2 ; exit code $3 ;;" >&2 echo "ERROR occured :: source ${1} ; line ${2} ; exit code ${3} ;;" >&2
_unset_vars _unset_vars
} }
@ -18,12 +18,26 @@ function _unset_vars()
unset USE_CONTAINER WISHED_CONFIG_PATH CONFIG_PATH VOLUME USE_TTY unset USE_CONTAINER WISHED_CONFIG_PATH CONFIG_PATH VOLUME USE_TTY
} }
CDIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)" function _get_current_directory()
{
if dirname "$(readlink -f "${0}")" &>/dev/null
then
CDIR="$(cd "$(dirname "$(readlink -f "${0}")")" && pwd)"
elif realpath -e -L "${0}" &>/dev/null
then
CDIR="$(realpath -e -L "${0}")"
CDIR="${CDIR%/setup.sh}"
fi
}
CDIR="$(pwd)"
_get_current_directory
CRI= CRI=
INFO= INFO=
IMAGE_NAME= IMAGE_NAME=
CONTAINER_NAME='mail' CONTAINER_NAME='mail'
DEFAULT_CONFIG_PATH="$CDIR/config" DEFAULT_CONFIG_PATH="${CDIR}/config"
USE_CONTAINER=false USE_CONTAINER=false
WISHED_CONFIG_PATH= WISHED_CONFIG_PATH=
CONFIG_PATH= CONFIG_PATH=
@ -32,7 +46,7 @@ USE_TTY=
function _check_root() function _check_root()
{ {
if [[ $EUID -ne 0 ]] if [[ ${EUID} -ne 0 ]]
then then
echo "Curently docker-mailserver doesn't support podman's rootless mode, please run this script as root user." echo "Curently docker-mailserver doesn't support podman's rootless mode, please run this script as root user."
return 1 return 1
@ -41,32 +55,32 @@ function _check_root()
function _update_config_path() function _update_config_path()
{ {
if [[ -n $CONTAINER_NAME ]] if [[ -n ${CONTAINER_NAME} ]]
then then
VOLUME=$(docker inspect "$CONTAINER_NAME" \ VOLUME=$(docker inspect "${CONTAINER_NAME}" \
--format="{{range .Mounts}}{{ println .Source .Destination}}{{end}}" | \ --format="{{range .Mounts}}{{ println .Source .Destination}}{{end}}" | \
grep "/tmp/docker-mailserver$" 2>/dev/null) grep "/tmp/docker-mailserver$" 2>/dev/null)
fi fi
if [[ -n $VOLUME ]] if [[ -n ${VOLUME} ]]
then then
CONFIG_PATH=$(echo "$VOLUME" | awk '{print $1}') CONFIG_PATH=$(echo "${VOLUME}" | awk '{print $1}')
fi fi
} }
function _inspect() function _inspect()
{ {
if _docker_image_exists "$IMAGE_NAME" if _docker_image_exists "${IMAGE_NAME}"
then then
echo "Image: $IMAGE_NAME" echo "Image: ${IMAGE_NAME}"
else else
echo "Image: '$IMAGE_NAME' cant be found." echo "Image: '${IMAGE_NAME}' cant be found."
fi fi
if [[ -n $CONTAINER_NAME ]] if [[ -n ${CONTAINER_NAME} ]]
then then
echo "Container: $CONTAINER_NAME" echo "Container: ${CONTAINER_NAME}"
echo "Config mount: $CONFIG_PATH" echo "Config mount: ${CONFIG_PATH}"
else else
echo "Container: Not running, please start docker-mailserver." echo "Container: Not running, please start docker-mailserver."
fi fi
@ -74,7 +88,7 @@ function _inspect()
function _usage() function _usage()
{ {
echo "Usage: $0 [-i IMAGE_NAME] [-c CONTAINER_NAME] <subcommand> <subcommand> [args] echo "Usage: ${0} [-i IMAGE_NAME] [-c CONTAINER_NAME] <subcommand> <subcommand> [args]
OPTIONS: OPTIONS:
@ -84,53 +98,52 @@ OPTIONS:
-c CONTAINER_NAME The name of the running container. -c CONTAINER_NAME The name of the running container.
-p PATH config folder path (default: $(pwd)/config) -p PATH config folder path (default: ${CDIR}/config)
SUBCOMMANDS: SUBCOMMANDS:
email: email:
$0 email add <email> [<password>] ${0} email add <email> [<password>]
$0 email update <email> [<password>] ${0} email update <email> [<password>]
$0 email del <email> ${0} email del <email>
$0 email restrict <add|del|list> <send|receive> [<email>] ${0} email restrict <add|del|list> <send|receive> [<email>]
$0 email list ${0} email list
alias: alias:
$0 alias add <email> <recipient> ${0} alias add <email> <recipient>
$0 alias del <email> <recipient> ${0} alias del <email> <recipient>
$0 alias list ${0} alias list
quota: quota:
$0 quota set <email> [<quota>] ${0} quota set <email> [<quota>]
$0 quota del <email> ${0} quota del <email>
config: config:
$0 config dkim <keysize> (default: 2048) ${0} config dkim <keysize> (default: 2048)
$0 config ssl <fqdn> ${0} config ssl <fqdn>
relay: relay:
$0 relay add-domain <domain> <host> [<port>] ${0} relay add-domain <domain> <host> [<port>]
$0 relay add-auth <domain> <username> [<password>] ${0} relay add-auth <domain> <username> [<password>]
$0 relay exclude-domain <domain> ${0} relay exclude-domain <domain>
debug: debug:
$0 debug fetchmail ${0} debug fetchmail
$0 debug fail2ban [<unban> <ip-address>] ${0} debug fail2ban [<unban> <ip-address>]
$0 debug show-mail-logs ${0} debug show-mail-logs
$0 debug inspect ${0} debug inspect
$0 debug login <commands> ${0} debug login <commands>
"
return 1 "
} }
function _docker_image_exists() function _docker_image_exists()
{ {
if $CRI history -q "$1" >/dev/null 2>&1 if ${CRI} history -q "${1}" >/dev/null 2>&1
then then
return 0 return 0
else else
@ -140,36 +153,36 @@ function _docker_image_exists()
function _docker_image() function _docker_image()
{ {
if $USE_CONTAINER if ${USE_CONTAINER}
then then
# reuse existing container specified on command line # reuse existing container specified on command line
$CRI exec "$USE_TTY" "$CONTAINER_NAME" "$@" ${CRI} exec "${USE_TTY}" "${CONTAINER_NAME}" "${@}"
else else
# start temporary container with specified image # start temporary container with specified image
if ! _docker_image_exists "$IMAGE_NAME" if ! _docker_image_exists "${IMAGE_NAME}"
then then
echo "Image '$IMAGE_NAME' not found. Pulling ..." echo "Image '${IMAGE_NAME}' not found. Pulling ..."
$CRI pull "$IMAGE_NAME" ${CRI} pull "${IMAGE_NAME}"
fi fi
${CRI} run --rm \ ${CRI} run --rm \
-v "$CONFIG_PATH":/tmp/docker-mailserver \ -v "${CONFIG_PATH}":/tmp/docker-mailserver \
"$USE_TTY" "$IMAGE_NAME" "$@" "${USE_TTY}" "${IMAGE_NAME}" "${@}"
fi fi
} }
function _docker_container() function _docker_container()
{ {
if [[ -n $CONTAINER_NAME ]] if [[ -n ${CONTAINER_NAME} ]]
then then
$CRI exec "$USE_TTY" "$CONTAINER_NAME" "$@" ${CRI} exec "${USE_TTY}" "${CONTAINER_NAME}" "${@}"
else else
echo "The docker-mailserver is not running!" echo "The docker-mailserver is not running!"
exit 1 exit 5
fi fi
} }
function main() function _main()
{ {
if [[ -n $(command -v docker) ]] if [[ -n $(command -v docker) ]]
then then
@ -180,10 +193,10 @@ function main()
_check_root _check_root
else else
echo "No supported Container Runtime Interface detected." echo "No supported Container Runtime Interface detected."
exit 1 exit 10
fi fi
INFO=$($CRI ps \ INFO=$(${CRI} ps \
--no-trunc \ --no-trunc \
--format "{{.Image}};{{.Names}}" \ --format "{{.Image}};{{.Names}}" \
--filter label=org.label-schema.name="docker-mailserver" | \ --filter label=org.label-schema.name="docker-mailserver" | \
@ -192,12 +205,12 @@ function main()
IMAGE_NAME=${INFO%;*} IMAGE_NAME=${INFO%;*}
CONTAINER_NAME=${INFO#*;} CONTAINER_NAME=${INFO#*;}
if [[ -z $IMAGE_NAME ]] if [[ -z ${IMAGE_NAME} ]]
then then
if [[ $CRI == "docker" ]] if [[ ${CRI} == "docker" ]]
then then
IMAGE_NAME=tvial/docker-mailserver:latest IMAGE_NAME=tvial/docker-mailserver:latest
elif [[ $CRI == "podman" ]] elif [[ ${CRI} == "podman" ]]
then then
IMAGE_NAME=docker.io/tvial/docker-mailserver:latest IMAGE_NAME=docker.io/tvial/docker-mailserver:latest
fi fi
@ -211,38 +224,38 @@ function main()
local OPTIND local OPTIND
while getopts ":c:i:p:" OPT while getopts ":c:i:p:" OPT
do do
case $OPT in case ${OPT} in
c) CONTAINER_NAME="$OPTARG" ; USE_CONTAINER=true ;; # container specified, connect to running instance c) CONTAINER_NAME="${OPTARG}" ; USE_CONTAINER=true ;; # container specified, connect to running instance
i) IMAGE_NAME="$OPTARG" ;; i) IMAGE_NAME="${OPTARG}" ;;
p) p)
case "$OPTARG" in case "${OPTARG}" in
/*) WISHED_CONFIG_PATH="$OPTARG" ;; /*) WISHED_CONFIG_PATH="${OPTARG}" ;;
* ) WISHED_CONFIG_PATH="$CDIR/$OPTARG" ;; * ) WISHED_CONFIG_PATH="${CDIR}/${OPTARG}" ;;
esac esac
if [[ ! -d $WISHED_CONFIG_PATH ]] if [[ ! -d ${WISHED_CONFIG_PATH} ]]
then then
echo "Directory doesn't exist" echo "Directory doesn't exist"
_usage _usage
exit 1 exit 40
fi fi
;; ;;
*) echo "Invalid option: -$OPTARG" >&2 ;; *) echo "Invalid option: -${OPTARG}" >&2 ;;
esac esac
done done
shift $((OPTIND-1)) shift $((OPTIND-1))
if [[ -z $WISHED_CONFIG_PATH ]] if [[ -z ${WISHED_CONFIG_PATH} ]]
then then
# no wished config path # no wished config path
_update_config_path _update_config_path
if [[ -z $CONFIG_PATH ]] if [[ -z ${CONFIG_PATH} ]]
then then
CONFIG_PATH=$DEFAULT_CONFIG_PATH CONFIG_PATH=${DEFAULT_CONFIG_PATH}
fi fi
else else
CONFIG_PATH=$WISHED_CONFIG_PATH CONFIG_PATH=${WISHED_CONFIG_PATH}
fi fi
@ -250,10 +263,10 @@ function main()
email) email)
shift ; case ${1:-} in shift ; case ${1:-} in
add ) shift ; _docker_image addmailuser "$@" ;; add ) shift ; _docker_image addmailuser "${@}" ;;
update ) shift ; _docker_image updatemailuser "$@" ;; update ) shift ; _docker_image updatemailuser "${@}" ;;
del ) shift ; _docker_image delmailuser "$@" ;; del ) shift ; _docker_image delmailuser "${@}" ;;
restrict ) shift ; _docker_container restrict-access "$@" ;; restrict ) shift ; _docker_container restrict-access "${@}" ;;
list ) _docker_image listmailuser ;; list ) _docker_image listmailuser ;;
* ) _usage ;; * ) _usage ;;
esac esac
@ -261,17 +274,17 @@ function main()
alias) alias)
shift ; case ${1:-} in shift ; case ${1:-} in
add ) shift ; _docker_image addalias "$@" ;; add ) shift ; _docker_image addalias "${1}" "${2}" ;;
del ) shift ; _docker_image delalias "$@" ;; del ) shift ; _docker_image delalias "${1}" "${2}" ;;
list ) shift ; _docker_image listalias "$@" ;; list ) shift ; _docker_image listalias ;;
* ) _usage ;; * ) _usage ;;
esac esac
;; ;;
quota) quota)
shift ; case ${1:-} in shift ; case ${1:-} in
set ) shift ; _docker_image setquota "$@" ;; set ) shift ; _docker_image setquota "${@}" ;;
del ) shift ; _docker_image delquota "$@" ;; del ) shift ; _docker_image delquota "${@}" ;;
* ) _usage ;; * ) _usage ;;
esac esac
;; ;;
@ -279,16 +292,16 @@ function main()
config) config)
shift ; case ${1:-} in shift ; case ${1:-} in
dkim ) _docker_image generate-dkim-config "${2:-2048}" ;; dkim ) _docker_image generate-dkim-config "${2:-2048}" ;;
ssl ) _docker_image generate-ssl-certificate "$2" ;; ssl ) _docker_image generate-ssl-certificate "${2}" ;;
* ) _usage ;; * ) _usage ;;
esac esac
;; ;;
relay) relay)
shift ; case ${1:-} in shift ; case ${1:-} in
add-domain ) shift ; _docker_image addrelayhost "$@" ;; add-domain ) shift ; _docker_image addrelayhost "${@}" ;;
add-auth ) shift ; _docker_image addsaslpassword "$@" ;; add-auth ) shift ; _docker_image addsaslpassword "${@}" ;;
exclude-domain ) shift ; _docker_image excluderelaydomain "$@" ;; exclude-domain ) shift ; _docker_image excluderelaydomain "${@}" ;;
* ) _usage ;; * ) _usage ;;
esac esac
;; ;;
@ -296,7 +309,7 @@ function main()
debug) debug)
shift ; case ${1:-} in shift ; case ${1:-} in
fetchmail ) _docker_image debug-fetchmail ;; fetchmail ) _docker_image debug-fetchmail ;;
fail2ban ) shift ; _docker_container fail2ban "$@" ;; fail2ban ) shift ; _docker_container fail2ban "${@}" ;;
show-mail-logs ) _docker_container cat /var/log/mail/mail.log ;; show-mail-logs ) _docker_container cat /var/log/mail/mail.log ;;
inspect ) _inspect ;; inspect ) _inspect ;;
login ) login )
@ -305,16 +318,16 @@ function main()
then then
_docker_container /bin/bash _docker_container /bin/bash
else else
_docker_container /bin/bash -c "$@" _docker_container /bin/bash -c "${@}"
fi fi
;; ;;
* ) _usage ;; * ) _usage ; exit 1 ;;
esac esac
;; ;;
* ) _usage ;; * ) _usage ; exit 1 ;;
esac esac
} }
main "$@" _main "${@}"
_unset_vars _unset_vars

View file

@ -1,203 +1,237 @@
#!/bin/bash #!/bin/bash
# version 0.1.0
#
# <INSERT TASK HERE>
# shellcheck source=/dev/null
. /usr/local/bin/helper_functions.sh . /usr/local/bin/helper_functions.sh
# create date for log output LOG_DATE=$(date +"%Y-%m-%d %H:%M:%S ")
log_date=$(date +"%Y-%m-%d %H:%M:%S ") echo "${LOG_DATE} Start check-for-changes script."
echo "${log_date} Start check-for-changes script."
# change directory # ? Checks ------------------------------------------------
cd /tmp/docker-mailserver
cd /tmp/docker-mailserver || exit 1
# Check postfix-accounts.cf exist else break # Check postfix-accounts.cf exist else break
if [ ! -f postfix-accounts.cf ]; then if [[ ! -f postfix-accounts.cf ]]
echo "${log_date} postfix-accounts.cf is missing! This should not run! Exit!" then
exit echo "${LOG_DATE} postfix-accounts.cf is missing! This should not run! Exit!"
exit
fi fi
# Verify checksum file exists; must be prepared by start-mailserver.sh # Verify checksum file exists; must be prepared by start-mailserver.sh
if [ ! -f $CHKSUM_FILE ]; then if [[ ! -f ${CHKSUM_FILE} ]]
echo "${log_date} ${CHKSUM_FILE} is missing! Start script failed? Exit!" then
exit echo "${LOG_DATE} ${CHKSUM_FILE} is missing! Start script failed? Exit!"
exit
fi fi
# ? Actual script begins ----------------------------------
# Determine postmaster address, duplicated from start-mailserver.sh # Determine postmaster address, duplicated from start-mailserver.sh
# This script previously didn't work when POSTMASTER_ADDRESS was empty # This script previously didn't work when POSTMASTER_ADDRESS was empty
if [[ -n "${OVERRIDE_HOSTNAME}" ]]; then if [[ -n ${OVERRIDE_HOSTNAME} ]]
DOMAINNAME=$(echo "${OVERRIDE_HOSTNAME}" | sed s/[^.]*.//) then
DOMAINNAME="${OVERRIDE_HOSTNAME#*.}"
else else
DOMAINNAME="$(hostname -d)" DOMAINNAME="$(hostname -d)"
fi fi
PM_ADDRESS="${POSTMASTER_ADDRESS:=postmaster@${DOMAINNAME}}" PM_ADDRESS="${POSTMASTER_ADDRESS:=postmaster@${DOMAINNAME}}"
echo "${log_date} Using postmaster address ${PM_ADDRESS}" echo "${LOG_DATE} Using postmaster address ${PM_ADDRESS}"
# Wait to make sure server is up before we start
sleep 10 sleep 10
# Run forever while true
while true; do do
LOG_DATE=$(date +"%Y-%m-%d %H:%M:%S ")
# recreate logdate # get chksum and check it, no need to lock config yet
log_date=$(date +"%Y-%m-%d %H:%M:%S ") _monitored_files_checksums >"${CHKSUM_FILE}.new"
# Get chksum and check it, no need to lock config yet if ! cmp --silent -- "${CHKSUM_FILE}" "${CHKSUM_FILE}.new"
monitored_files_checksums >"$CHKSUM_FILE.new" then
echo "${LOG_DATE} Change detected"
changed=$(grep -Fxvf "${CHKSUM_FILE}" "${CHKSUM_FILE}.new" | sed 's/^[^ ]\+ //')
mv "${CHKSUM_FILE}.new" "${CHKSUM_FILE}"
if ! cmp --silent -- "$CHKSUM_FILE" "$CHKSUM_FILE.new"; then # Bug alert! This overwrites the alias set by start-mailserver.sh
echo "${log_date} Change detected" # Take care that changes in one script are propagated to the other
changed=$(grep -Fxvf "$CHKSUM_FILE" "$CHKSUM_FILE.new" | sed 's/^[^ ]\+ //')
mv "$CHKSUM_FILE.new" "$CHKSUM_FILE"
# Bug alert! This overwrites the alias set by start-mailserver.sh # ! NEEDS FIX -----------------------------------------
# Take care that changes in one script are propagated to the other # TODO FIX --------------------------------------------
# Also note that changes are performed in place and are not atomic # ! NEEDS EXTENSIONS ----------------------------------
# We should fix that and write to temporary files, stop, swap and start # TODO Perform updates below conditionally too --------
# Also note that changes are performed in place and are not atomic
# We should fix that and write to temporary files, stop, swap and start
# Lock configuration while working
(
flock -e 200
# Lock configuration while working for file in ${changed}
# Not fixing indentation yet to reduce diff (fix later in separate commit) do
( case ${file} in
flock -e 200 /etc/letsencrypt/acme.json)
for certdomain in ${SSL_DOMAIN} ${HOSTNAME} ${DOMAINNAME}
for file in $changed; do do
case $file in if _extract_certs_from_acme "${certdomain}"
/etc/letsencrypt/acme.json) then
for certdomain in $SSL_DOMAIN $HOSTNAME $DOMAINNAME; do break
if extractCertsFromAcmeJson "$certdomain"; then fi
break done
fi ;;
* ) _notify 'warn' 'file not found for certificate in check_for_changes.sh' ;;
esac
done done
;;
#TODO: Perform updates below conditionally as well.
esac
done
#regen postix aliases. # regenerate postix aliases
echo "root: ${PM_ADDRESS}" > /etc/aliases echo "root: ${PM_ADDRESS}" >/etc/aliases
if [ -f /tmp/docker-mailserver/postfix-aliases.cf ]; then if [[ -f /tmp/docker-mailserver/postfix-aliases.cf ]]
cat /tmp/docker-mailserver/postfix-aliases.cf>>/etc/aliases then
fi cat /tmp/docker-mailserver/postfix-aliases.cf >>/etc/aliases
postalias /etc/aliases fi
postalias /etc/aliases
#regen postfix accounts. # regenerate postfix accounts
echo -n > /etc/postfix/vmailbox echo -n >/etc/postfix/vmailbox
echo -n > /etc/dovecot/userdb echo -n >/etc/dovecot/userdb
if [ -f /tmp/docker-mailserver/postfix-accounts.cf -a "$ENABLE_LDAP" != 1 ]; then if [[ -f /tmp/docker-mailserver/postfix-accounts.cf ]] && [[ ${ENABLE_LDAP} -ne 1 ]]
sed -i 's/\r//g' /tmp/docker-mailserver/postfix-accounts.cf then
echo "# WARNING: this file is auto-generated. Modify config/postfix-accounts.cf to edit user list." > /etc/postfix/vmailbox sed -i 's/\r//g' /tmp/docker-mailserver/postfix-accounts.cf
# Checking that /tmp/docker-mailserver/postfix-accounts.cf ends with a newline echo "# WARNING: this file is auto-generated. Modify config/postfix-accounts.cf to edit user list." >/etc/postfix/vmailbox
sed -i -e '$a\' /tmp/docker-mailserver/postfix-accounts.cf
chown dovecot:dovecot /etc/dovecot/userdb
chmod 640 /etc/dovecot/userdb
sed -i -e '/\!include auth-ldap\.conf\.ext/s/^/#/' /etc/dovecot/conf.d/10-auth.conf
sed -i -e '/\!include auth-passwdfile\.inc/s/^#//' /etc/dovecot/conf.d/10-auth.conf
# rebuild relay host # Checking that /tmp/docker-mailserver/postfix-accounts.cf ends with a newline
if [ ! -z "$RELAY_HOST" ]; then # shellcheck disable=SC1003
# keep old config sed -i -e '$a\' /tmp/docker-mailserver/postfix-accounts.cf
echo -n > /etc/postfix/sasl_passwd chown dovecot:dovecot /etc/dovecot/userdb
if [ ! -z "$SASL_PASSWD" ]; then chmod 640 /etc/dovecot/userdb
echo "$SASL_PASSWD" >> /etc/postfix/sasl_passwd sed -i -e '/\!include auth-ldap\.conf\.ext/s/^/#/' /etc/dovecot/conf.d/10-auth.conf
fi sed -i -e '/\!include auth-passwdfile\.inc/s/^#//' /etc/dovecot/conf.d/10-auth.conf
# add domain-specific auth from config file
if [ -f /tmp/docker-mailserver/postfix-sasl-password.cf ]; then
(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-sasl-password.cf || true) | while read line; do
if ! echo "$line" | grep -q -e "\s*#"; then
echo "$line" >> /etc/postfix/sasl_passwd
fi
done
fi
# add default relay
if [ ! -z "$RELAY_USER" ] && [ ! -z "$RELAY_PASSWORD" ]; then
echo "[$RELAY_HOST]:$RELAY_PORT $RELAY_USER:$RELAY_PASSWORD" >> /etc/postfix/sasl_passwd
fi
fi
# Creating users # rebuild relay host
# 'pass' is encrypted if [[ -n ${RELAY_HOST} ]]
# comments and empty lines are ignored then
grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-accounts.cf | while IFS=$'|' read login pass # keep old config
do echo -n >/etc/postfix/sasl_passwd
# Setting variables for better readability if [[ -n ${SASL_PASSWD} ]]
user=$(echo ${login} | cut -d @ -f1) then
domain=$(echo ${login} | cut -d @ -f2) echo "${SASL_PASSWD}" >>/etc/postfix/sasl_passwd
fi
user_attributes="" # add domain-specific auth from config file
# test if user has a defined quota if [[ -f /tmp/docker-mailserver/postfix-sasl-password.cf ]]
if [ -f /tmp/docker-mailserver/dovecot-quotas.cf ]; then then
user_quota=($(grep "${user}@${domain}:" -i /tmp/docker-mailserver/dovecot-quotas.cf | tr ':' '\n')) (grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-sasl-password.cf || true) | while read -r line
do
if ! echo "${line}" | grep -q -e "\s*#"
then
echo "${line}" >>/etc/postfix/sasl_passwd
fi
done
fi
if [ ${#user_quota[@]} -eq 2 ]; then # add default relay
user_attributes="${user_attributes}userdb_quota_rule=*:bytes=${user_quota[1]}" if [[ -n "${RELAY_USER}" ]] && [[ -n "${RELAY_PASSWORD}" ]]
fi then
fi echo "[${RELAY_HOST}]:${RELAY_PORT} ${RELAY_USER}:${RELAY_PASSWORD}" >>/etc/postfix/sasl_passwd
fi
fi
# Let's go! # creating users ; 'pass' is encrypted
echo "${login} ${domain}/${user}/" >> /etc/postfix/vmailbox # comments and empty lines are ignored
# User database for dovecot has the following format: grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-accounts.cf | while IFS=$'|' read -r login pass
# user:password:uid:gid:(gecos):home:(shell):extra_fields do
# Example : user=$(echo "${login}" | cut -d @ -f1)
# ${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::userdb_mail=maildir:/var/mail/${domain}/${user} domain=$(echo "${login}" | cut -d @ -f2)
echo "${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::${user_attributes}" >> /etc/dovecot/userdb
mkdir -p /var/mail/${domain}/${user}
# Copy user provided sieve file, if present user_attributes=""
test -e /tmp/docker-mailserver/${login}.dovecot.sieve && cp /tmp/docker-mailserver/${login}.dovecot.sieve /var/mail/${domain}/${user}/.dovecot.sieve # test if user has a defined quota
echo ${domain} >> /tmp/vhost.tmp if [[ -f /tmp/docker-mailserver/dovecot-quotas.cf ]]
done then
fi declare -a USER_QUOTA
if [ ! -z "$RELAY_HOST" ]; then IFS=':' ; read -r -a USER_QUOTA < <(grep "${user}@${domain}:" -i /tmp/docker-mailserver/dovecot-quotas.cf)
populate_relayhost_map unset IFS
fi
if [ -f /etc/postfix/sasl_passwd ]; then
chown root:root /etc/postfix/sasl_passwd
chmod 0600 /etc/postfix/sasl_passwd
fi
if [ -f postfix-virtual.cf ]; then
# regen postfix aliases
echo -n > /etc/postfix/virtual
echo -n > /etc/postfix/regexp
if [ -f /tmp/docker-mailserver/postfix-virtual.cf ]; then
# Copying virtual file
cp -f /tmp/docker-mailserver/postfix-virtual.cf /etc/postfix/virtual
(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-virtual.cf || true) | while read from to
do
# Setting variables for better readability
uname=$(echo ${from} | cut -d @ -f1)
domain=$(echo ${from} | cut -d @ -f2)
# if they are equal it means the line looks like: "user1 other@domain.tld"
test "$uname" != "$domain" && echo ${domain} >> /tmp/vhost.tmp
done
fi
if [ -f /tmp/docker-mailserver/postfix-regexp.cf ]; then
# Copying regexp alias file
cp -f /tmp/docker-mailserver/postfix-regexp.cf /etc/postfix/regexp
sed -i -e '/^virtual_alias_maps/{
s/ regexp:.*//
s/$/ regexp:\/etc\/postfix\/regexp/
}' /etc/postfix/main.cf
fi
fi
# Set vhost
if [ -f /tmp/vhost.tmp ]; then
cat /tmp/vhost.tmp | sort | uniq > /etc/postfix/vhost && rm /tmp/vhost.tmp
fi
# Set right new if needed [[ ${#USER_QUOTA[@]} -eq 2 ]] && user_attributes="${user_attributes}userdb_quota_rule=*:bytes=${USER_QUOTA[1]}"
if [ `find /var/mail -maxdepth 3 -a \( \! -user 5000 -o \! -group 5000 \) | grep -c .` != 0 ]; then fi
chown -R 5000:5000 /var/mail
fi
# Restart of the postfix echo "${login} ${domain}/${user}/" >>/etc/postfix/vmailbox
supervisorctl restart postfix
# Prevent restart of dovecot when smtp_only=1 # user database for dovecot has the following format:
if [ ! $SMTP_ONLY = 1 ]; then # user:password:uid:gid:(gecos):home:(shell):extra_fields
supervisorctl restart dovecot # example :
fi # ${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::userdb_mail=maildir:/var/mail/${domain}/${user}
echo "${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::${user_attributes}" >>/etc/dovecot/userdb
mkdir -p "/var/mail/${domain}/${user}"
) 200<postfix-accounts.cf # end lock if [[ -e /tmp/docker-mailserver/${login}.dovecot.sieve ]]
fi then
cp "/tmp/docker-mailserver/${login}.dovecot.sieve" "/var/mail/${domain}/${user}/.dovecot.sieve"
fi
sleep 1 echo "${domain}" >>/tmp/vhost.tmp
done
fi
[[ -n ${RELAY_HOST} ]] && _populate_relayhost_map
if [[ -f /etc/postfix/sasl_passwd ]]
then
chown root:root /etc/postfix/sasl_passwd
chmod 0600 /etc/postfix/sasl_passwd
fi
if [[ -f postfix-virtual.cf ]]
then
# regenerate postfix aliases
echo -n >/etc/postfix/virtual
echo -n >/etc/postfix/regexp
if [[ -f /tmp/docker-mailserver/postfix-virtual.cf ]]
then
cp -f /tmp/docker-mailserver/postfix-virtual.cf /etc/postfix/virtual
# the `to` seems to be important; don't delete it
# shellcheck disable=SC2034
(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-virtual.cf || true) | while read -r from to
do
uname=$(echo "${from}" | cut -d @ -f1)
domain=$(echo "${from}" | cut -d @ -f2)
# if they are equal it means the line looks like: "user1 other@domain.tld"
[ "${uname}" != "${domain}" ] && echo "${domain}" >>/tmp/vhost.tmp
done
fi
if [[ -f /tmp/docker-mailserver/postfix-regexp.cf ]]
then
cp -f /tmp/docker-mailserver/postfix-regexp.cf /etc/postfix/regexp
sed -i -e '/^virtual_alias_maps/{
s/ regexp:.*//
s/$/ regexp:\/etc\/postfix\/regexp/
}' /etc/postfix/main.cf
fi
fi
if [[ -f /tmp/vhost.tmp ]]
then
# shellcheck disable=SC2002
cat /tmp/vhost.tmp | sort | uniq >/etc/postfix/vhost && rm /tmp/vhost.tmp
fi
if [[ $(find /var/mail -maxdepth 3 -a \( \! -user 5000 -o \! -group 5000 \) | grep -c .) -ne 0 ]]
then
chown -R 5000:5000 /var/mail
fi
supervisorctl restart postfix
# prevent restart of dovecot when smtp_only=1
[[ ${SMTP_ONLY} -ne 1 ]] && supervisorctl restart dovecot
) 200<postfix-accounts.cf # end lock
fi
sleep 1
done done

View file

@ -1,5 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# fail2ban-wrapper.sh, version 0.0.1
# version 0.1.0
# #
# You cannot start fail2ban in some foreground mode and # You cannot start fail2ban in some foreground mode and
# it's more or less important that docker doesn't kill # it's more or less important that docker doesn't kill
@ -21,14 +22,12 @@ trap "/usr/bin/fail2ban-client stop" SIGINT
trap "/usr/bin/fail2ban-client stop" SIGTERM trap "/usr/bin/fail2ban-client stop" SIGTERM
trap "/usr/bin/fail2ban-client reload" SIGHUP trap "/usr/bin/fail2ban-client reload" SIGHUP
# start fail2ban
/usr/bin/fail2ban-client start /usr/bin/fail2ban-client start
# lets give fail2ban some time to start
sleep 5 sleep 5
# wait until fail2ban is dead (triggered by trap) # wait until fail2ban is dead (triggered by trap)
while kill -0 "`cat /var/run/fail2ban/fail2ban.pid`"; do while kill -0 "$(cat /var/run/fail2ban/fail2ban.pid)"
do
sleep 5 sleep 5
done done

View file

@ -1,45 +1,62 @@
#!/bin/bash #!/bin/bash
# expects mask prefix length and the digit # version 0.1.1
function _mask_ip_digit() { #
if [[ $1 -ge 8 ]]; then # Provides varous helpers.
# ? IP and CIDR -------------------------------------------
function _mask_ip_digit()
{
if [[ ${1} -ge 8 ]]
then
MASK=255 MASK=255
elif [[ ${1} -le 0 ]]
then
MASK=0
else else
if [[ $1 -le 0 ]]; then VALUES=(0 128 192 224 240 248 252 254 255)
MASK=0 MASK=${VALUES[${1}]}
else
VALUES=('0' '128' '192' '224' '240' '248' '252' '254' '255')
MASK=${VALUES[$1]}
fi
fi fi
echo $(($2 & $MASK))
local DVAL=${2}
((DVAL&=MASK))
echo "${DVAL}"
} }
# transforms a specific ip with CIDR suffix like 1.2.3.4/16 # Transforms a specific IP with CIDR suffix
# to subnet with cidr suffix like 1.2.0.0/16 # like 1.2.3.4/16 to subnet with cidr suffix
function _sanitize_ipv4_to_subnet_cidr() { # like 1.2.0.0/16.
IP=${1%%/*} # Assumes correct IP and subnet are provided.
PREFIX_LENGTH=${1#*/} function _sanitize_ipv4_to_subnet_cidr()
{
local DIGIT_PREFIX_LENGTH="${1#*/}"
# split IP by . into digits declare -a MASKED_DIGITS
DIGITS=(${IP//./ }) declare -a DIGITS
IFS='.' ; read -r -a DIGITS < <(echo "${1%%/*}") ; unset IFS
# mask digits according to prefix length for ((i = 0 ; i < 4 ; i++))
MASKED_DIGITS=() do
DIGIT_PREFIX_LENGTH="$PREFIX_LENGTH" MASKED_DIGITS[i]=$(_mask_ip_digit "${DIGIT_PREFIX_LENGTH}" "${DIGITS[i]}")
for DIGIT in "${DIGITS[@]}"; do DIGIT_PREFIX_LENGTH=$((DIGIT_PREFIX_LENGTH - 8))
MASKED_DIGITS+=($(_mask_ip_digit $DIGIT_PREFIX_LENGTH $DIGIT))
DIGIT_PREFIX_LENGTH=$(($DIGIT_PREFIX_LENGTH - 8))
done done
# output masked ip plus prefix length echo "${MASKED_DIGITS[0]}.${MASKED_DIGITS[1]}.${MASKED_DIGITS[2]}.${MASKED_DIGITS[3]}/${1#*/}"
echo ${MASKED_DIGITS[0]}.${MASKED_DIGITS[1]}.${MASKED_DIGITS[2]}.${MASKED_DIGITS[3]}/$PREFIX_LENGTH
} }
export -f _sanitize_ipv4_to_subnet_cidr
# extracts certificates from acme.json and returns 0 if found
function extractCertsFromAcmeJson() {
WHAT=$1
# ? ACME certs --------------------------------------------
function _extract_certs_from_acme()
{
local KEY
# shellcheck disable=SC2002
KEY=$(cat /etc/letsencrypt/acme.json | python -c " KEY=$(cat /etc/letsencrypt/acme.json | python -c "
import sys,json import sys,json
acme = json.load(sys.stdin) acme = json.load(sys.stdin)
@ -47,10 +64,13 @@ for key, value in acme.items():
certs = value['Certificates'] certs = value['Certificates']
for cert in certs: for cert in certs:
if 'domain' in cert and 'key' in cert: if 'domain' in cert and 'key' in cert:
if 'main' in cert['domain'] and cert['domain']['main'] == '$WHAT' or 'sans' in cert['domain'] and '$WHAT' in cert['domain']['sans']: if 'main' in cert['domain'] and cert['domain']['main'] == '${1}' or 'sans' in cert['domain'] and '${1}' in cert['domain']['sans']:
print cert['key'] print cert['key']
break break
") ")
local CERT
# shellcheck disable=SC2002
CERT=$(cat /etc/letsencrypt/acme.json | python -c " CERT=$(cat /etc/letsencrypt/acme.json | python -c "
import sys,json import sys,json
acme = json.load(sys.stdin) acme = json.load(sys.stdin)
@ -58,26 +78,35 @@ for key, value in acme.items():
certs = value['Certificates'] certs = value['Certificates']
for cert in certs: for cert in certs:
if 'domain' in cert and 'certificate' in cert: if 'domain' in cert and 'certificate' in cert:
if 'main' in cert['domain'] and cert['domain']['main'] == '$WHAT' or 'sans' in cert['domain'] and '$WHAT' in cert['domain']['sans']: if 'main' in cert['domain'] and cert['domain']['main'] == '${1}' or 'sans' in cert['domain'] and '${1}' in cert['domain']['sans']:
print cert['certificate'] print cert['certificate']
break break
") ")
if [[ -n "${KEY}${CERT}" ]]; then if [[ -n "${KEY}${CERT}" ]]
mkdir -p /etc/letsencrypt/live/"$HOSTNAME"/ then
echo $KEY | base64 -d >/etc/letsencrypt/live/"$HOSTNAME"/key.pem || exit 1 mkdir -p "/etc/letsencrypt/live/${HOSTNAME}/"
echo $CERT | base64 -d >/etc/letsencrypt/live/"$HOSTNAME"/fullchain.pem || exit 1
echo "Cert found in /etc/letsencrypt/acme.json for $WHAT" echo "${KEY}" | base64 -d >/etc/letsencrypt/live/"${HOSTNAME}"/key.pem || exit 1
echo "${CERT}" | base64 -d >/etc/letsencrypt/live/"${HOSTNAME}"/fullchain.pem || exit 1
echo "Cert found in /etc/letsencrypt/acme.json for ${1}"
return 0 return 0
else else
return 1 return 1
fi fi
} }
export -f _extract_certs_from_acme
# ? Notification ------------------------------------------
declare -A DEFAULT_VARS declare -A DEFAULT_VARS
DEFAULT_VARS["DMS_DEBUG"]="${DMS_DEBUG:="0"}" DEFAULT_VARS["DMS_DEBUG"]="${DMS_DEBUG:=0}"
function notify () { function _notify()
{
c_red="\e[0;31m" c_red="\e[0;31m"
c_green="\e[0;32m" c_green="\e[0;32m"
c_brown="\e[0;33m" c_brown="\e[0;33m"
@ -85,101 +114,101 @@ function notify () {
c_bold="\033[1m" c_bold="\033[1m"
c_reset="\e[0m" c_reset="\e[0m"
notification_type=$1 notification_type=${1}
notification_msg=$2 notification_msg=${2}
notification_format=$3 notification_format=${3}
msg="" msg=""
case "${notification_type}" in case "${notification_type}" in
'taskgrp') 'taskgrp' ) msg="${c_bold}${notification_msg}${c_reset}" ;;
msg="${c_bold}${notification_msg}${c_reset}" 'task' )
;; if [[ ${DEFAULT_VARS["DMS_DEBUG"]} -eq 1 ]]
'task') then
if [[ ${DEFAULT_VARS["DMS_DEBUG"]} == 1 ]]; then
msg=" ${notification_msg}${c_reset}" msg=" ${notification_msg}${c_reset}"
fi fi
;; ;;
'inf') 'inf' )
if [[ ${DEFAULT_VARS["DMS_DEBUG"]} == 1 ]]; then if [[ ${DEFAULT_VARS["DMS_DEBUG"]} -eq 1 ]]
then
msg="${c_green} * ${notification_msg}${c_reset}" msg="${c_green} * ${notification_msg}${c_reset}"
fi fi
;; ;;
'started') 'started' ) msg="${c_green} ${notification_msg}${c_reset}" ;;
msg="${c_green} ${notification_msg}${c_reset}" 'warn' ) msg="${c_brown} Warning ${notification_msg}${c_reset}" ;;
;; 'err' ) msg="${c_blue} Error ${notification_msg}${c_reset}" ;;
'warn') 'fatal' ) msg="${c_red} Fatal Error: ${notification_msg}${c_reset}" ;;
msg="${c_brown} * ${notification_msg}${c_reset}" * ) msg="" ;;
;;
'err')
msg="${c_red} * ${notification_msg}${c_reset}"
;;
'fatal')
msg="${c_red}Error: ${notification_msg}${c_reset}"
;;
*)
msg=""
;;
esac esac
case "${notification_format}" in case "${notification_format}" in
'n') 'n' ) options="-ne" ;;
options="-ne" * ) options="-e" ;;
;;
*)
options="-e"
;;
esac esac
[[ ! -z "${msg}" ]] && echo $options "${msg}" [[ -n "${msg}" ]] && echo "${options}" "${msg}"
} }
export -f _notify
# ? Relay Host Map ----------------------------------------
# setup /etc/postfix/relayhost_map # setup /etc/postfix/relayhost_map
# -- # --
# @domain1.com [smtp.mailgun.org]:587 # @domain1.com [smtp.mailgun.org]:587
# @domain2.com [smtp.mailgun.org]:587 # @domain2.com [smtp.mailgun.org]:587
# @domain3.com [smtp.mailgun.org]:587 # @domain3.com [smtp.mailgun.org]:587
function populate_relayhost_map() { function _populate_relayhost_map()
{
echo -n > /etc/postfix/relayhost_map echo -n > /etc/postfix/relayhost_map
chown root:root /etc/postfix/relayhost_map chown root:root /etc/postfix/relayhost_map
chmod 0600 /etc/postfix/relayhost_map chmod 0600 /etc/postfix/relayhost_map
if [ -f /tmp/docker-mailserver/postfix-relaymap.cf ]; then if [[ -f /tmp/docker-mailserver/postfix-relaymap.cf ]]
notify 'inf' "Adding relay mappings from postfix-relaymap.cf" then
# Keep lines which are not a comment *and* have a destination. _notify 'inf' "Adding relay mappings from postfix-relaymap.cf"
sed -n '/^\s*[^#[:space:]]\S*\s\+\S/p' /tmp/docker-mailserver/postfix-relaymap.cf \ # keep lines which are not a comment *and* have a destination.
>> /etc/postfix/relayhost_map sed -n '/^\s*[^#[:space:]]\S*\s\+\S/p' /tmp/docker-mailserver/postfix-relaymap.cf >> /etc/postfix/relayhost_map
fi fi
{ {
# Note: Won't detect domains when lhs has spaces (but who does that?!). # note: won't detect domains when lhs has spaces (but who does that?!)
sed -n '/^\s*[^#[:space:]]/ s/^[^@|]*@\([^|]\+\)|.*$/\1/p' /tmp/docker-mailserver/postfix-accounts.cf sed -n '/^\s*[^#[:space:]]/ s/^[^@|]*@\([^|]\+\)|.*$/\1/p' /tmp/docker-mailserver/postfix-accounts.cf
[ -f /tmp/docker-mailserver/postfix-virtual.cf ] &&
sed -n '/^\s*[^#[:space:]]/ s/^\s*[^@[:space:]]*@\(\S\+\)\s.*/\1/p' /tmp/docker-mailserver/postfix-virtual.cf [ -f /tmp/docker-mailserver/postfix-virtual.cf ] && sed -n '/^\s*[^#[:space:]]/ s/^\s*[^@[:space:]]*@\(\S\+\)\s.*/\1/p' /tmp/docker-mailserver/postfix-virtual.cf
} | while read domain; do } | while read -r domain
if ! grep -q -e "^@${domain}\b" /etc/postfix/relayhost_map && do
! grep -qs -e "^\s*@${domain}\s*$" /tmp/docker-mailserver/postfix-relaymap.cf; then # domain not already present *and* not ignored
# Domain not already present *and* not ignored. if ! grep -q -e "^@${domain}\b" /etc/postfix/relayhost_map && ! grep -qs -e "^\s*@${domain}\s*$" /tmp/docker-mailserver/postfix-relaymap.cf
notify 'inf' "Adding relay mapping for ${domain}" then
echo "@${domain} [$RELAY_HOST]:$RELAY_PORT" >> /etc/postfix/relayhost_map _notify 'inf' "Adding relay mapping for ${domain}"
echo "@${domain} [${RELAY_HOST}]:${RELAY_PORT}" >> /etc/postfix/relayhost_map
fi fi
done done
} }
export -f _populate_relayhost_map
# File storing the checksums of the monitored files.
# ? File checksums ----------------------------------------
# file storing the checksums of the monitored files.
# shellcheck disable=SC2034
CHKSUM_FILE=/tmp/docker-mailserver-config-chksum CHKSUM_FILE=/tmp/docker-mailserver-config-chksum
# Compute checksums of monitored files. # Compute checksums of monitored files.
function monitored_files_checksums() { function _monitored_files_checksums()
{
( (
cd /tmp/docker-mailserver cd /tmp/docker-mailserver || exit 1
# (2>/dev/null to ignore warnings about files that don't exist)
exec sha512sum 2>/dev/null -- \ exec sha512sum 2>/dev/null -- \
postfix-accounts.cf \ postfix-accounts.cf \
postfix-virtual.cf \ postfix-virtual.cf \
postfix-aliases.cf \ postfix-aliases.cf \
dovecot-quotas.cf \ dovecot-quotas.cf \
/etc/letsencrypt/acme.json \ /etc/letsencrypt/acme.json \
"/etc/letsencrypt/live/$HOSTNAME/key.pem" \ "/etc/letsencrypt/live/${HOSTNAME}/key.pem" \
"/etc/letsencrypt/live/$HOSTNAME/fullchain.pem" "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem"
) )
return 0
} }
export -f _monitored_files_checksums

View file

@ -1,5 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# postfix-wrapper.sh, version 0.1.0
# version 0.1.0
# #
# You cannot start postfix in some foreground mode and # You cannot start postfix in some foreground mode and
# it's more or less important that docker doesn't kill # it's more or less important that docker doesn't kill
@ -21,14 +22,11 @@ trap "service postfix stop" SIGINT
trap "service postfix stop" SIGTERM trap "service postfix stop" SIGTERM
trap "service postfix reload" SIGHUP trap "service postfix reload" SIGHUP
# start postfix
service postfix start service postfix start
# lets give postfix some time to start
sleep 5 sleep 5
# wait until postfix is dead (triggered by trap) # wait until postfix is dead (triggered by trap)
while kill -0 "`cat /var/spool/postfix/pid/master.pid`"; do while kill -0 "$(cat /var/spool/postfix/pid/master.pid)"
do
sleep 5 sleep 5
done done

View file

@ -1,44 +1,53 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# postsrsd-wrapper.sh, version 0.2.2
if [ -n "$SRS_DOMAINNAME" ]; then # version 0.1.0
domain_name="$SRS_DOMAINNAME"
elif [ -n "$OVERRIDE_HOSTNAME" ]; then
domain_name="${OVERRIDE_HOSTNAME#*.}"
elif [ -n "$DOMAINNAME" ]; then
domain_name="$DOMAINNAME"
else
domain_name=$(hostname -d)
fi
sed -i -e "s/localdomain/${domain_name}/g" /etc/default/postsrsd function _generate_secret()
{
postsrsd_secret_file='/etc/postsrsd.secret' ( umask 0077 ; dd if=/dev/urandom bs=24 count=1 2>/dev/null | base64 -w0 > "${1}" )
postsrsd_state_dir='/var/mail-state/etc-postsrsd'
postsrsd_state_secret_file="${postsrsd_state_dir}/postsrsd.secret"
generate_secret() {
( umask 0077
dd if=/dev/urandom bs=24 count=1 2>/dev/null | base64 -w0 > "$1" )
} }
if [ -n "$SRS_SECRET" ]; then if [[ -n ${SRS_DOMAINNAME} ]]
( umask 0077 then
echo "$SRS_SECRET" | tr ',' '\n' > "$postsrsd_secret_file" ) NEW_DOMAIN_NAME="${SRS_DOMAINNAME}"
elif [[ -n ${OVERRIDE_HOSTNAME} ]]
then
NEW_DOMAIN_NAME="${OVERRIDE_HOSTNAME#*.}"
elif [[ -n ${DOMAINNAME} ]]
then
NEW_DOMAIN_NAME="${DOMAINNAME}"
else else
if [ "$ONE_DIR" = 1 ]; then NEW_DOMAIN_NAME=$(hostname -d)
if [ ! -f "$postsrsd_state_secret_file" ]; then fi
install -d -m 0775 "$postsrsd_state_dir"
generate_secret "$postsrsd_state_secret_file" sed -i -e "s/localdomain/${NEW_DOMAIN_NAME}/g" /etc/default/postsrsd
POSTSRSD_SECRET_FILE='/etc/postsrsd.secret'
POSTSRSD_STATE_DIR='/var/mail-state/etc-postsrsd'
POSTSRSD_STATE_SECRET_FILE="${POSTSRSD_STATE_DIR}/postsrsd.secret"
if [[ -n ${SRS_SECRET} ]]
then
( umask 0077 ; echo "${SRS_SECRET}" | tr ',' '\n' > "${POSTSRSD_SECRET_FILE}" )
else
if [[ ${ONE_DIR} -eq 1 ]]
then
if [[ ! -f ${POSTSRSD_STATE_SECRET_FILE} ]]
then
install -d -m 0775 "${POSTSRSD_STATE_DIR}"
_generate_secret "${POSTSRSD_STATE_SECRET_FILE}"
fi fi
install -m 0400 "$postsrsd_state_secret_file" "$postsrsd_secret_file"
elif [ ! -f "$postsrsd_secret_file" ]; then install -m 0400 "${POSTSRSD_STATE_SECRET_FILE}" "${POSTSRSD_SECRET_FILE}"
generate_secret "$postsrsd_secret_file" elif [[ ! -f ${POSTSRSD_SECRET_FILE} ]]
then
_generate_secret "${POSTSRSD_SECRET_FILE}"
fi fi
fi fi
if [ -n "$SRS_EXCLUDE_DOMAINS" ]; then if [[ -n ${SRS_EXCLUDE_DOMAINS} ]]
sed -i -e "s/^#\?SRS_EXCLUDE_DOMAINS=.*$/SRS_EXCLUDE_DOMAINS=$SRS_EXCLUDE_DOMAINS/g" /etc/default/postsrsd then
sed -i -e "s/^#\?SRS_EXCLUDE_DOMAINS=.*$/SRS_EXCLUDE_DOMAINS=${SRS_EXCLUDE_DOMAINS}/g" /etc/default/postsrsd
fi fi
/etc/init.d/postsrsd start /etc/init.d/postsrsd start

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@
[supervisord] [supervisord]
nodaemon=true nodaemon=true
strip_ansi=true
[program:mailserver] [program:mailserver]
startsecs=0 startsecs=0