tests(chore): Rename test files to serial and parallel types

- `test_helper.bats` needs more work than this PR provides to be compatible with parallel tests, so must remain as a serial test for now.
- `spam_bounced.bats` had failures as a serial test, but works well converted to a parallel test in a future commit.
This commit is contained in:
Georg Lauterbach 2022-11-03 19:05:40 +01:00 committed by Brennan Kinney
parent 2bc4078e35
commit 59127e2b25
41 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,79 @@
load 'test_helper/common'
TEST_NAME_PREFIX='ClamAV:'
CONTAINER_NAME='dms-test-clamav'
RUN_COMMAND=('run' 'docker' 'exec' "${CONTAINER_NAME}")
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . "${CONTAINER_NAME}")
docker run --rm --detach --tty \
--name "${CONTAINER_NAME}" \
--hostname mail.my-domain.com \
--volume "${PRIVATE_CONFIG}:/tmp/docker-mailserver" \
--volume "${PWD}/test/test-files:/tmp/docker-mailserver-test:ro" \
--env ENABLE_AMAVIS=1 \
--env AMAVIS_LOGLEVEL=2 \
--env ENABLE_CLAMAV=1 \
--env ENABLE_UPDATE_CHECK=0 \
--env ENABLE_SPAMASSASSIN=0 \
--env ENABLE_FAIL2BAN=0 \
--env PERMIT_DOCKER=host \
--env CLAMAV_MESSAGE_SIZE_LIMIT=30M \
--env LOG_LEVEL=debug \
"${IMAGE_NAME}"
wait_for_finished_setup_in_container "${CONTAINER_NAME}"
# wait for ClamAV to be fully setup or we will get errors on the log
repeat_in_container_until_success_or_timeout 60 "${CONTAINER_NAME}" test -e /var/run/clamav/clamd.ctl
wait_for_service "${CONTAINER_NAME}" postfix
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
"${RUN_COMMAND[@]}" bash -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-virus.txt"
assert_success
wait_for_empty_mail_queue_in_container "${CONTAINER_NAME}"
}
function teardown_file() {
docker rm -f "${CONTAINER_NAME}"
}
@test "${TEST_NAME_PREFIX} process clamd is running" {
"${RUN_COMMAND[@]}" bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
assert_success
}
@test "${TEST_NAME_PREFIX} log files exist at /var/log/mail directory" {
"${RUN_COMMAND[@]}" bash -c "ls -1 /var/log/mail/ | grep -E 'clamav|freshclam|mail.log'| wc -l"
assert_success
assert_output 3
}
@test "${TEST_NAME_PREFIX} should be identified by Amavis" {
"${RUN_COMMAND[@]}" grep -i 'Found secondary av scanner ClamAV-clamscan' /var/log/mail/mail.log
assert_success
}
@test "${TEST_NAME_PREFIX} freshclam cron is enabled" {
"${RUN_COMMAND[@]}" bash -c "grep '/usr/bin/freshclam' -r /etc/cron.d"
assert_success
}
@test "${TEST_NAME_PREFIX} env CLAMAV_MESSAGE_SIZE_LIMIT is set correctly" {
"${RUN_COMMAND[@]}" grep -q '^MaxFileSize 30M$' /etc/clamav/clamd.conf
assert_success
}
@test "${TEST_NAME_PREFIX} rejects virus" {
"${RUN_COMMAND[@]}" bash -c "grep 'Blocked INFECTED' /var/log/mail/mail.log | grep '<virus@external.tld> -> <user1@localhost.localdomain>'"
assert_success
}
@test "${TEST_NAME_PREFIX} process clamd restarts when killed" {
"${RUN_COMMAND[@]}" bash -c "pkill clamd && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
assert_success
}

View file

@ -0,0 +1,28 @@
load 'test_helper/common'
function setup() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container relay-hosts)
docker run -d --name mail_with_default_relay \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e DEFAULT_RELAY_HOST=default.relay.host.invalid:25 \
-e PERMIT_DOCKER=host \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_with_default_relay
}
function teardown() {
docker rm -f mail_with_default_relay
}
#
# default relay host
#
@test "checking default relay host: default relay host is added to main.cf" {
run docker exec mail_with_default_relay /bin/sh -c 'grep -e "^relayhost =" /etc/postfix/main.cf'
assert_output 'relayhost = default.relay.host.invalid:25'
}

View file

@ -0,0 +1,49 @@
load 'test_helper/common'
# Globals referenced from `test_helper/common`:
# TEST_NAME
# Can run tests in parallel?: No
# Shared static container name: TEST_NAME
# Test case
# ---------
# When SPAMASSASSIN_SPAM_TO_INBOX=0, spam messages must be bounced (rejected).
# SPAMASSASSIN_SPAM_TO_INBOX=1 is covered in `mail_spam_junk_folder.bats`.
# Original test PR: https://github.com/docker-mailserver/docker-mailserver/pull/1485
function teardown() {
docker rm -f "${TEST_NAME}"
}
function setup_file() {
init_with_defaults
}
# Not used
# function teardown_file() {
# }
@test "checking amavis: spam message is bounced (rejected)" {
# shellcheck disable=SC2034
local TEST_DOCKER_ARGS=(
--env ENABLE_SPAMASSASSIN=1
--env PERMIT_DOCKER=container
--env SPAMASSASSIN_SPAM_TO_INBOX=0
)
common_container_setup 'TEST_DOCKER_ARGS'
_should_bounce_spam
}
function _should_bounce_spam() {
wait_for_smtp_port_in_container_to_respond "${TEST_NAME}"
# send a spam message
run docker exec "${TEST_NAME}" /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
assert_success
# message will be added to a queue with varying delay until amavis receives it
run repeat_until_success_or_timeout 60 sh -c "docker logs ${TEST_NAME} | grep 'Blocked SPAM {NoBounceInbound,Quarantined}'"
assert_success
}

View file

@ -0,0 +1,54 @@
load 'test_helper/common'
function setup_file() {
local PRIVATE_CONFIG
export ALL IPV4 IPV6
PRIVATE_CONFIG=$(duplicate_config_for_container . "${IPV4}")
ALL="mail_dovecot_all_protocols"
IPV4="mail_dovecot_ipv4"
IPV6="mail_dovecot_ipv6"
docker run --rm -d --name "${ALL}" \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-e DOVECOT_INET_PROTOCOLS= \
-h mail.my-domain.com \
-t "${NAME}"
docker run --rm -d --name "${IPV4}" \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-e DOVECOT_INET_PROTOCOLS=ipv4 \
-h mail.my-domain.com \
-t "${NAME}"
docker run --rm -d --name "${IPV6}" \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-e DOVECOT_INET_PROTOCOLS=ipv6 \
-h mail.my-domain.com \
-t "${NAME}"
}
@test 'checking dovecot IP configuration' {
wait_for_finished_setup_in_container "${ALL}"
run docker exec "${ALL}" grep '^#listen = \*, ::' /etc/dovecot/dovecot.conf
assert_success
assert_output '#listen = *, ::'
}
@test 'checking dovecot IPv4 configuration' {
wait_for_finished_setup_in_container "${IPV4}"
run docker exec "${IPV4}" grep '^listen = \*$' /etc/dovecot/dovecot.conf
assert_success
assert_output 'listen = *'
}
@test 'checking dovecot IPv6 configuration' {
wait_for_finished_setup_in_container "${IPV6}"
run docker exec "${IPV6}" grep '^listen = \[::\]$' /etc/dovecot/dovecot.conf
assert_success
assert_output 'listen = [::]'
}
function teardown_file {
docker rm -f "${ALL}" "${IPV4}" "${IPV6}"
}

View file

@ -0,0 +1,30 @@
load 'test_helper/common'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_helper_functions \
--cap-add=NET_ADMIN \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_FETCHMAIL=1 \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_helper_functions
}
function teardown_file() {
docker rm -f mail_helper_functions
}
@test "check helper functions (network.sh): _sanitize_ipv4_to_subnet_cidr" {
run docker exec mail_helper_functions bash -c "source /usr/local/bin/helpers/index.sh; _sanitize_ipv4_to_subnet_cidr 255.255.255.255/0"
assert_output "0.0.0.0/0"
run docker exec mail_helper_functions bash -c "source /usr/local/bin/helpers/index.sh; _sanitize_ipv4_to_subnet_cidr 192.168.255.14/20"
assert_output "192.168.240.0/20"
run docker exec mail_helper_functions bash -c "source /usr/local/bin/helpers/index.sh; _sanitize_ipv4_to_subnet_cidr 192.168.255.14/32"
assert_output "192.168.255.14/32"
}

View file

@ -0,0 +1,87 @@
load 'test_helper/common'
# Note if tests fail asserting against `supervisorctl tail changedetector` output,
# use `supervisorctl tail -<num bytes> changedetector` instead to increase log output.
# Default `<num bytes>` appears to be around 1500.
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_changedetector_one)
docker run -d --name mail_changedetector_one \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e LOG_LEVEL=trace \
-h mail.my-domain.com -t "${NAME}"
docker run -d --name mail_changedetector_two \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e LOG_LEVEL=trace \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_changedetector_one
wait_for_finished_setup_in_container mail_changedetector_two
}
function teardown_file() {
docker rm -f mail_changedetector_one
docker rm -f mail_changedetector_two
}
@test "checking changedetector: servers are ready" {
wait_for_service mail_changedetector_one changedetector
wait_for_service mail_changedetector_two changedetector
}
@test "checking changedetector: can detect changes & between two containers using same config" {
echo "" >> "$(private_config_path mail_changedetector_one)/postfix-accounts.cf"
sleep 25
run docker exec mail_changedetector_one /bin/bash -c "supervisorctl tail -3000 changedetector"
assert_output --partial "postfix: stopped"
assert_output --partial "postfix: started"
assert_output --partial "Change detected"
assert_output --partial "Removed lock"
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl tail -3000 changedetector"
assert_output --partial "postfix: stopped"
assert_output --partial "postfix: started"
assert_output --partial "Change detected"
assert_output --partial "Removed lock"
}
@test "checking changedetector: lock file found, blocks, and doesn't get prematurely removed" {
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl stop changedetector"
docker exec mail_changedetector_one /bin/bash -c "touch /tmp/docker-mailserver/check-for-changes.sh.lock"
echo "" >> "$(private_config_path mail_changedetector_one)/postfix-accounts.cf"
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl start changedetector"
sleep 15
run docker exec mail_changedetector_one /bin/bash -c "supervisorctl tail changedetector"
assert_output --partial "another execution of 'check-for-changes.sh' is happening"
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl tail changedetector"
assert_output --partial "another execution of 'check-for-changes.sh' is happening"
# Ensure starting a new check-for-changes.sh instance (restarting here) doesn't delete the lock
docker exec mail_changedetector_two /bin/bash -c "rm -f /var/log/supervisor/changedetector.log"
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl restart changedetector"
sleep 5
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl tail changedetector"
refute_output --partial "another execution of 'check-for-changes.sh' is happening"
refute_output --partial "Removed lock"
}
@test "checking changedetector: lock stale and cleaned up" {
docker rm -f mail_changedetector_two
docker exec mail_changedetector_one /bin/bash -c "touch /tmp/docker-mailserver/check-for-changes.sh.lock"
echo "" >> "$(private_config_path mail_changedetector_one)/postfix-accounts.cf"
sleep 15
run docker exec mail_changedetector_one /bin/bash -c "supervisorctl tail changedetector"
assert_output --partial "another execution of 'check-for-changes.sh' is happening"
sleep 65
run docker exec mail_changedetector_one /bin/bash -c "supervisorctl tail -3000 changedetector"
assert_output --partial "removing stale lock file"
}

View file

@ -0,0 +1,48 @@
load 'test_helper/common'
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run --rm -d --name mail_disabled_clamav_spamassassin \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_CLAMAV=0 \
-e ENABLE_SPAMASSASSIN=0 \
-e AMAVIS_LOGLEVEL=2 \
-h mail.my-domain.com -t "${NAME}"
# TODO: find a better way to know when we have waited long enough
# for ClamAV to should have come up, if it were enabled
wait_for_smtp_port_in_container mail_disabled_clamav_spamassassin
docker exec mail_disabled_clamav_spamassassin /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
}
teardown_file() {
docker rm -f mail_disabled_clamav_spamassassin
}
@test "checking process: ClamAV (ClamAV disabled by ENABLED_CLAMAV=0)" {
run docker exec mail_disabled_clamav_spamassassin /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
assert_failure
}
@test "checking spamassassin: should not be listed in amavis when disabled" {
run docker exec mail_disabled_clamav_spamassassin /bin/sh -c "grep -i 'ANTI-SPAM-SA code' /var/log/mail/mail.log | grep 'NOT loaded'"
assert_success
}
@test "checking ClamAV: should not be listed in amavis when disabled" {
run docker exec mail_disabled_clamav_spamassassin grep -i 'Found secondary av scanner ClamAV-clamscan' /var/log/mail/mail.log
assert_failure
}
@test "checking ClamAV: should not be called when disabled" {
run docker exec mail_disabled_clamav_spamassassin grep -i 'connect to /var/run/clamav/clamd.ctl failed' /var/log/mail/mail.log
assert_failure
}
@test "checking restart of process: ClamAV (ClamAV disabled by ENABLED_CLAMAV=0)" {
run docker exec mail_disabled_clamav_spamassassin /bin/bash -c "pkill -f clamd && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
assert_failure
}

View file

@ -0,0 +1,61 @@
load 'test_helper/common'
CONTAINER="mail_dnsbl_enabled"
CONTAINER2="mail_dnsbl_disabled"
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . "${CONTAINER}")
docker run --rm -d --name "${CONTAINER}" \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-e ENABLE_DNSBL=1 \
-h mail.my-domain.com \
-t "${NAME}"
docker run --rm -d --name "${CONTAINER2}" \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-e ENABLE_DNSBL=0 \
-h mail.my-domain.com \
-t "${NAME}"
wait_for_smtp_port_in_container "${CONTAINER}"
wait_for_smtp_port_in_container "${CONTAINER2}"
}
# ENABLE_DNSBL=1
@test "checking enabled postfix DNS block list zen.spamhaus.org" {
run docker exec "${CONTAINER}" postconf smtpd_recipient_restrictions
assert_output --partial 'reject_rbl_client zen.spamhaus.org'
}
@test "checking enabled postscreen DNS block lists --> postscreen_dnsbl_action" {
run docker exec "${CONTAINER}" postconf postscreen_dnsbl_action
assert_output 'postscreen_dnsbl_action = enforce'
}
@test "checking enabled postscreen DNS block lists --> postscreen_dnsbl_sites" {
run docker exec "${CONTAINER}" postconf postscreen_dnsbl_sites
assert_output 'postscreen_dnsbl_sites = zen.spamhaus.org=127.0.0.[2..11]*3 bl.mailspike.net=127.0.0.[2;14;13;12;11;10] b.barracudacentral.org*2 bl.spameatingmonkey.net=127.0.0.2 dnsbl.sorbs.net psbl.surriel.com list.dnswl.org=127.0.[0..255].0*-2 list.dnswl.org=127.0.[0..255].1*-3 list.dnswl.org=127.0.[0..255].[2..3]*-4'
}
# ENABLE_DNSBL=0
@test "checking disabled postfix DNS block list zen.spamhaus.org" {
run docker exec "${CONTAINER2}" postconf smtpd_recipient_restrictions
refute_output --partial 'reject_rbl_client zen.spamhaus.org'
}
@test "checking disabled postscreen DNS block lists --> postscreen_dnsbl_action" {
run docker exec "${CONTAINER2}" postconf postscreen_dnsbl_action
assert_output 'postscreen_dnsbl_action = ignore'
}
@test "checking disabled postscreen DNS block lists --> postscreen_dnsbl_sites" {
run docker exec "${CONTAINER2}" postconf postscreen_dnsbl_sites
assert_output 'postscreen_dnsbl_sites ='
}
# cleanup
function teardown_file() {
docker rm -f "${CONTAINER}" "${CONTAINER2}"
}

View file

@ -0,0 +1,200 @@
load 'test_helper/common'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run --rm -d --name mail_fail2ban \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_FAIL2BAN=1 \
-e POSTSCREEN_ACTION=ignore \
--cap-add=NET_ADMIN \
--hostname mail.my-domain.com \
--tty \
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)" \
"${NAME}"
# Create a container which will send wrong authentications and should get banned
docker run --name fail-auth-mailer \
-e MAIL_FAIL2BAN_IP="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' mail_fail2ban)" \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test \
-d "${NAME}" \
tail -f /var/log/faillog
wait_for_finished_setup_in_container mail_fail2ban
}
function teardown_file() {
docker rm -f mail_fail2ban fail-auth-mailer
}
#
# processes
#
@test "checking process: fail2ban (fail2ban server enabled)" {
run docker exec mail_fail2ban /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/bin/python3 /usr/bin/fail2ban-server'"
assert_success
}
#
# fail2ban
#
@test "checking fail2ban: localhost is not banned because ignored" {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep 'IP list:.*127.0.0.1'"
assert_failure
run docker exec mail_fail2ban /bin/sh -c "grep 'ignoreip = 127.0.0.1/8' /etc/fail2ban/jail.conf"
assert_success
}
@test "checking fail2ban: fail2ban-fail2ban.cf overrides" {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get loglevel | grep DEBUG"
assert_success
}
@test "checking fail2ban: fail2ban-jail.cf overrides" {
FILTERS=(dovecot postfix postfix-sasl)
for FILTER in "${FILTERS[@]}"; do
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get ${FILTER} bantime"
assert_output 1234
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get ${FILTER} findtime"
assert_output 321
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get ${FILTER} maxretry"
assert_output 2
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'dovecot', 'addaction', 'nftables-multiport']\""
assert_output "['set', 'dovecot', 'addaction', 'nftables-multiport']"
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'postfix', 'addaction', 'nftables-multiport']\""
assert_output "['set', 'postfix', 'addaction', 'nftables-multiport']"
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'postfix-sasl', 'addaction', 'nftables-multiport']\""
assert_output "['set', 'postfix-sasl', 'addaction', 'nftables-multiport']"
done
}
@test "checking fail2ban: ban ip on multiple failed login" {
# can't pipe the file as usual due to postscreen. (respecting postscreen_greet_wait time and talking in turn):
# shellcheck disable=SC1004
for _ in {1,2}
do
docker exec fail-auth-mailer /bin/bash -c \
'exec 3<>/dev/tcp/${MAIL_FAIL2BAN_IP}/25 && \
while IFS= read -r cmd; do \
head -1 <&3; \
[[ ${cmd} == "EHLO"* ]] && sleep 6; \
echo ${cmd} >&3; \
done < "/tmp/docker-mailserver-test/auth/smtp-auth-login-wrong.txt"'
done
sleep 5
FAIL_AUTH_MAILER_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' fail-auth-mailer)
# Checking that FAIL_AUTH_MAILER_IP is banned in mail_fail2ban
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep '${FAIL_AUTH_MAILER_IP}'"
assert_success
# Checking that FAIL_AUTH_MAILER_IP is banned by nftables
run docker exec mail_fail2ban /bin/sh -c "nft list set inet f2b-table addr-set-postfix-sasl"
assert_output --partial "elements = { ${FAIL_AUTH_MAILER_IP} }"
}
@test "checking fail2ban: unban ip works" {
FAIL_AUTH_MAILER_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' fail-auth-mailer)
docker exec mail_fail2ban fail2ban-client set postfix-sasl unbanip "${FAIL_AUTH_MAILER_IP}"
sleep 5
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep 'IP list:.*${FAIL_AUTH_MAILER_IP}'"
assert_failure
# Checking that FAIL_AUTH_MAILER_IP is unbanned by nftables
run docker exec mail_fail2ban /bin/sh -c "nft list set inet f2b-table addr-set-postfix-sasl"
refute_output --partial "${FAIL_AUTH_MAILER_IP}"
}
@test "checking fail2ban ban" {
# Ban single IP address
run docker exec mail_fail2ban fail2ban ban 192.0.66.7
assert_success
assert_output "Banned custom IP: 1"
run docker exec mail_fail2ban fail2ban
assert_success
assert_output --regexp "Banned in custom:.*192\.0\.66\.7"
run docker exec mail_fail2ban nft list set inet f2b-table addr-set-custom
assert_success
assert_output --partial "elements = { 192.0.66.7 }"
run docker exec mail_fail2ban fail2ban unban 192.0.66.7
assert_success
assert_output --partial "Unbanned IP from custom: 1"
run docker exec mail_fail2ban nft list set inet f2b-table addr-set-custom
refute_output --partial "192.0.66.7"
# Ban IP network
run docker exec mail_fail2ban fail2ban ban 192.0.66.0/24
assert_success
assert_output "Banned custom IP: 1"
run docker exec mail_fail2ban fail2ban
assert_success
assert_output --regexp "Banned in custom:.*192\.0\.66\.0/24"
run docker exec mail_fail2ban nft list set inet f2b-table addr-set-custom
assert_success
assert_output --partial "elements = { 192.0.66.0/24 }"
run docker exec mail_fail2ban fail2ban unban 192.0.66.0/24
assert_success
assert_output --partial "Unbanned IP from custom: 1"
run docker exec mail_fail2ban nft list set inet f2b-table addr-set-custom
refute_output --partial "192.0.66.0/24"
}
@test "checking FAIL2BAN_BLOCKTYPE is really set to drop" {
run docker exec mail_fail2ban bash -c 'nft list table inet f2b-table'
assert_success
assert_output --partial 'tcp dport { 110, 143, 465, 587, 993, 995, 4190 } ip saddr @addr-set-dovecot drop'
assert_output --partial 'tcp dport { 25, 110, 143, 465, 587, 993, 995 } ip saddr @addr-set-postfix-sasl drop'
assert_output --partial 'tcp dport { 25, 110, 143, 465, 587, 993, 995, 4190 } ip saddr @addr-set-custom drop'
}
@test "checking setup.sh: setup.sh fail2ban" {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client set dovecot banip 192.0.66.4"
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client set dovecot banip 192.0.66.5"
sleep 10
run ./setup.sh -c mail_fail2ban fail2ban
assert_output --regexp '^Banned in dovecot:.*192\.0\.66\.4'
assert_output --regexp '^Banned in dovecot:.*192\.0\.66\.5'
run ./setup.sh -c mail_fail2ban fail2ban unban 192.0.66.4
assert_output --partial "Unbanned IP from dovecot: 1"
run ./setup.sh -c mail_fail2ban fail2ban
assert_output --regexp "^Banned in dovecot:.*192\.0\.66\.5"
run ./setup.sh -c mail_fail2ban fail2ban unban 192.0.66.5
assert_output --partial "Unbanned IP from dovecot: 1"
run ./setup.sh -c mail_fail2ban fail2ban unban
assert_output --partial "You need to specify an IP address: Run"
}
#
# supervisor
#
@test "checking restart of process: fail2ban (fail2ban server enabled)" {
run docker exec mail_fail2ban /bin/bash -c "pkill fail2ban && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/bin/python3 /usr/bin/fail2ban-server'"
assert_success
}

View file

@ -0,0 +1,51 @@
load 'test_helper/common'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_fetchmail \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_FETCHMAIL=1 \
--cap-add=NET_ADMIN \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_fetchmail
}
function teardown_file() {
docker rm -f mail_fetchmail
}
#
# processes
#
@test "checking process: fetchmail (fetchmail server enabled)" {
run docker exec mail_fetchmail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/bin/fetchmail'"
assert_success
}
#
# fetchmail
#
@test "checking fetchmail: gerneral options in fetchmailrc are loaded" {
run docker exec mail_fetchmail grep 'set syslog' /etc/fetchmailrc
assert_success
}
@test "checking fetchmail: fetchmail.cf is loaded" {
run docker exec mail_fetchmail grep 'pop3.example.com' /etc/fetchmailrc
assert_success
}
#
# supervisor
#
@test "checking restart of process: fetchmail" {
run docker exec mail_fetchmail /bin/bash -c "pkill fetchmail && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/bin/fetchmail'"
assert_success
}

View file

@ -0,0 +1,82 @@
load 'test_helper/common'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_fetchmail_parallel \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_FETCHMAIL=1 \
-e FETCHMAIL_PARALLEL=1 \
--cap-add=NET_ADMIN \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_fetchmail_parallel
}
function teardown_file() {
docker rm -f mail_fetchmail_parallel
}
#
# processes
#
@test "checking process: fetchmail 1 (fetchmail server enabled)" {
run docker exec mail_fetchmail_parallel /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/bin/fetchmail -f /etc/fetchmailrc.d/fetchmail-1.rc'"
assert_success
}
@test "checking process: fetchmail 2 (fetchmail server enabled)" {
run docker exec mail_fetchmail_parallel /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/bin/fetchmail -f /etc/fetchmailrc.d/fetchmail-2.rc'"
assert_success
}
#
# fetchmail
#
@test "checking fetchmail: gerneral options in fetchmail-1.rc are loaded" {
run docker exec mail_fetchmail_parallel grep 'set syslog' /etc/fetchmailrc.d/fetchmail-1.rc
assert_success
}
@test "checking fetchmail: gerneral options in fetchmail-2.rc are loaded" {
run docker exec mail_fetchmail_parallel grep 'set syslog' /etc/fetchmailrc.d/fetchmail-2.rc
assert_success
}
@test "checking fetchmail: fetchmail-1.rc is loaded with pop3.example.com" {
run docker exec mail_fetchmail_parallel grep 'pop3.example.com' /etc/fetchmailrc.d/fetchmail-1.rc
assert_success
}
@test "checking fetchmail: fetchmail-1.rc is loaded without pop3-2.example.com" {
run docker exec mail_fetchmail_parallel grep 'pop3-2.example.com' /etc/fetchmailrc.d/fetchmail-1.rc
assert_failure
}
@test "checking fetchmail: fetchmail-2.rc is loaded without pop3.example.com" {
run docker exec mail_fetchmail_parallel grep 'pop3.example.com' /etc/fetchmailrc.d/fetchmail-2.rc
assert_failure
}
@test "checking fetchmail: fetchmail-2.rc is loaded with pop3-2.example.com" {
run docker exec mail_fetchmail_parallel grep 'pop3-2.example.com' /etc/fetchmailrc.d/fetchmail-2.rc
assert_success
}
#
# supervisor
#
@test "checking restart of process: fetchmail-1" {
run docker exec mail_fetchmail_parallel /bin/bash -c "pkill fetchmail && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/bin/fetchmail -f /etc/fetchmailrc.d/fetchmail-1.rc'"
assert_success
}
@test "checking restart of process: fetchmail-2" {
run docker exec mail_fetchmail_parallel /bin/bash -c "pkill fetchmail && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/bin/fetchmail -f /etc/fetchmailrc.d/fetchmail-2.rc'"
assert_success
}

View file

@ -0,0 +1,214 @@
load 'test_helper/common'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_override_hostname)
docker run --rm -d --name mail_override_hostname \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e PERMIT_DOCKER=network \
-e ENABLE_SRS=1 \
-e OVERRIDE_HOSTNAME=mail.my-domain.com \
--hostname unknown.domain.tld \
--tty \
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)" \
"${NAME}"
PRIVATE_CONFIG_TWO=$(duplicate_config_for_container . mail_non_subdomain_hostname)
docker run --rm -d --name mail_non_subdomain_hostname \
-v "${PRIVATE_CONFIG_TWO}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e PERMIT_DOCKER=network \
-e ENABLE_SRS=1 \
--hostname domain.com \
--tty \
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)" \
"${NAME}"
PRIVATE_CONFIG_THREE=$(duplicate_config_for_container . mail_srs_domainname)
docker run --rm -d --name mail_srs_domainname \
-v "${PRIVATE_CONFIG_THREE}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e PERMIT_DOCKER=network \
-e ENABLE_SRS=1 \
-e SRS_DOMAINNAME='srs.my-domain.com' \
--domainname 'my-domain.com' \
--hostname 'mail' \
--tty \
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)" \
"${NAME}"
PRIVATE_CONFIG_FOUR=$(duplicate_config_for_container . mail_domainname)
docker run --rm -d --name mail_domainname \
-v "${PRIVATE_CONFIG_FOUR}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e PERMIT_DOCKER=network \
-e ENABLE_SRS=1 \
--domainname 'my-domain.com' \
--hostname 'mail' \
--tty \
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)" \
"${NAME}"
wait_for_smtp_port_in_container mail_override_hostname
wait_for_smtp_port_in_container mail_non_subdomain_hostname
wait_for_smtp_port_in_container mail_srs_domainname
wait_for_smtp_port_in_container mail_domainname
# 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_non_subdomain_hostname /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
}
function teardown_file() {
# Running `docker rm -f` too soon after `docker stop` can result in failure during teardown with:
# "Error response from daemon: removal of container mail_domainname is already in progress"
sleep 1
docker rm -f mail_override_hostname mail_non_subdomain_hostname mail_srs_domainname mail_domainname
}
@test "checking SRS: SRS_DOMAINNAME is used correctly" {
repeat_until_success_or_timeout 15 docker exec mail_srs_domainname grep "SRS_DOMAIN=srs.my-domain.com" /etc/default/postsrsd
}
@test "checking SRS: DOMAINNAME is handled correctly" {
repeat_until_success_or_timeout 15 docker exec mail_domainname grep "SRS_DOMAIN=my-domain.com" /etc/default/postsrsd
}
@test "checking configuration: hostname/domainname override: check container hostname is applied correctly" {
run docker exec mail_override_hostname /bin/bash -c "hostname | grep unknown.domain.tld"
assert_success
}
@test "checking configuration: hostname/domainname override: check overriden hostname is applied to all configs" {
run docker exec mail_override_hostname /bin/bash -c "cat /etc/mailname | grep my-domain.com"
assert_success
run docker exec mail_override_hostname /bin/bash -c "postconf -n | grep mydomain | grep my-domain.com"
assert_success
run docker exec mail_override_hostname /bin/bash -c "postconf -n | grep myhostname | grep mail.my-domain.com"
assert_success
run docker exec mail_override_hostname /bin/bash -c "doveconf | grep hostname | grep mail.my-domain.com"
assert_success
run docker exec mail_override_hostname /bin/bash -c "cat /etc/opendmarc.conf | grep AuthservID | grep mail.my-domain.com"
assert_success
run docker exec mail_override_hostname /bin/bash -c "cat /etc/opendmarc.conf | grep TrustedAuthservIDs | grep mail.my-domain.com"
assert_success
run docker exec mail_override_hostname /bin/bash -c "cat /etc/amavis/conf.d/05-node_id | grep myhostname | grep mail.my-domain.com"
assert_success
}
@test "checking configuration: hostname/domainname override: check hostname in postfix HELO message" {
run docker exec mail_override_hostname /bin/bash -c "nc -w 1 0.0.0.0 25 | grep mail.my-domain.com"
assert_success
}
@test "checking configuration: hostname/domainname override: check headers of received mail" {
run docker exec mail_override_hostname /bin/sh -c "ls -A /var/mail/localhost.localdomain/user1/new | wc -l | grep 1"
assert_success
run docker exec mail_override_hostname /bin/sh -c "cat /var/mail/localhost.localdomain/user1/new/* | grep mail.my-domain.com"
assert_success
# test whether the container hostname is not found in received mail
run docker exec mail_override_hostname /bin/sh -c "cat /var/mail/localhost.localdomain/user1/new/* | grep unknown.domain.tld"
assert_failure
}
@test "checking SRS: OVERRIDE_HOSTNAME is handled correctly" {
run docker exec mail_override_hostname grep "SRS_DOMAIN=my-domain.com" /etc/default/postsrsd
assert_success
}
@test "checking dovecot: postmaster address" {
run docker exec mail_override_hostname /bin/sh -c "grep 'postmaster_address = postmaster@my-domain.com' /etc/dovecot/conf.d/15-lda.conf"
assert_success
}
#
# non-subdomain tests
#
@test "checking configuration: non-subdomain: check container hostname is applied correctly" {
run docker exec mail_non_subdomain_hostname /bin/bash -c "hostname | grep domain.com"
assert_success
}
@test "checking configuration: non-subdomain: check overriden hostname is applied to all configs" {
run docker exec mail_non_subdomain_hostname /bin/bash -c "cat /etc/mailname | grep domain.com"
assert_success
run docker exec mail_non_subdomain_hostname /bin/bash -c "postconf -n | grep mydomain | grep domain.com"
assert_success
run docker exec mail_non_subdomain_hostname /bin/bash -c "postconf -n | grep myhostname | grep domain.com"
assert_success
run docker exec mail_non_subdomain_hostname /bin/bash -c "doveconf | grep hostname | grep domain.com"
assert_success
run docker exec mail_non_subdomain_hostname /bin/bash -c "cat /etc/opendmarc.conf | grep AuthservID | grep domain.com"
assert_success
run docker exec mail_non_subdomain_hostname /bin/bash -c "cat /etc/opendmarc.conf | grep TrustedAuthservIDs | grep domain.com"
assert_success
run docker exec mail_non_subdomain_hostname /bin/bash -c "cat /etc/amavis/conf.d/05-node_id | grep myhostname | grep domain.com"
assert_success
}
@test "checking configuration: non-subdomain: check hostname in postfix HELO message" {
run docker exec mail_non_subdomain_hostname /bin/bash -c "nc -w 1 0.0.0.0 25 | grep domain.com"
assert_success
}
@test "checking configuration: non-subdomain: check headers of received mail" {
run docker exec mail_non_subdomain_hostname /bin/sh -c "ls -A /var/mail/localhost.localdomain/user1/new | wc -l | grep 1"
assert_success
run docker exec mail_non_subdomain_hostname /bin/sh -c "cat /var/mail/localhost.localdomain/user1/new/* | grep domain.com"
assert_success
}
@test "checking SRS: non-subdomain is handled correctly" {
docker exec mail_non_subdomain_hostname cat /etc/default/postsrsd
run docker exec mail_non_subdomain_hostname grep "SRS_DOMAIN=domain.com" /etc/default/postsrsd
assert_success
}
@test "checking dovecot: non-subdomain postmaster address" {
run docker exec mail_non_subdomain_hostname /bin/sh -c "grep 'postmaster_address = postmaster@domain.com' /etc/dovecot/conf.d/15-lda.conf"
assert_success
}
#
# clean exit
#
@test "checking that the container stops cleanly: mail_override_hostname" {
run docker stop -t 60 mail_override_hostname
assert_success
}
@test "checking that the container stops cleanly: mail_non_subdomain_hostname" {
run docker stop -t 60 mail_non_subdomain_hostname
assert_success
}
@test "checking that the container stops cleanly: mail_srs_domainname" {
run docker stop -t 60 mail_srs_domainname
assert_success
}
@test "checking that the container stops cleanly: mail_domainname" {
run docker stop -t 60 mail_domainname
assert_success
}

View file

@ -0,0 +1,44 @@
load 'test_helper/common'
setup_file() {
local PRIVATE_CONFIG PRIVATE_ETC
PRIVATE_CONFIG=$(duplicate_config_for_container .)
PRIVATE_ETC=$(duplicate_config_for_container dovecot-lmtp/ mail_lmtp_ip_dovecot-lmtp)
docker run -d --name mail_lmtp_ip \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "${PRIVATE_ETC}":/etc/dovecot \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_POSTFIX_VIRTUAL_TRANSPORT=1 \
-e POSTFIX_DAGENT=lmtp:127.0.0.1:24 \
-e PERMIT_DOCKER=container \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_lmtp_ip
}
teardown_file() {
docker rm -f mail_lmtp_ip
}
#
# Postfix VIRTUAL_TRANSPORT
#
@test "checking postfix-lmtp: virtual_transport config is set" {
run docker exec mail_lmtp_ip /bin/sh -c "grep 'virtual_transport = lmtp:127.0.0.1:24' /etc/postfix/main.cf"
assert_success
}
@test "checking postfix-lmtp: delivers mail to existing account" {
# maybe we can move this into the setup to speed things up futher.
# this likely would need an async coroutine to avoid blocking the other tests while waiting for the server to come up
wait_for_smtp_port_in_container mail_lmtp_ip
run docker exec mail_lmtp_ip /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
assert_success
# polling needs to avoid wc -l's unconditionally successful return status
repeat_until_success_or_timeout 60 docker exec mail_lmtp_ip /bin/sh -c "grep 'postfix/lmtp' /var/log/mail/mail.log | grep 'status=sent' | grep ' Saved)'"
run docker exec mail_lmtp_ip /bin/sh -c "grep 'postfix/lmtp' /var/log/mail/mail.log | grep 'status=sent' | grep ' Saved)' | wc -l"
assert_success
assert_output 1
}

View file

@ -0,0 +1,85 @@
load 'test_helper/common'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_pop3 \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_POP3=1 \
-e PERMIT_DOCKER=container \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_pop3
}
function teardown_file() {
docker rm -f mail_pop3
}
#
# pop
#
@test "checking pop: server is ready" {
run docker exec mail_pop3 /bin/bash -c "nc -w 1 0.0.0.0 110 | grep '+OK'"
assert_success
}
@test "checking pop: authentication works" {
run docker exec mail_pop3 /bin/sh -c "nc -w 1 0.0.0.0 110 < /tmp/docker-mailserver-test/auth/pop3-auth.txt"
assert_success
}
@test "checking pop: added user authentication works" {
run docker exec mail_pop3 /bin/sh -c "nc -w 1 0.0.0.0 110 < /tmp/docker-mailserver-test/auth/added-pop3-auth.txt"
assert_success
}
#
# spamassassin
#
@test "checking spamassassin: docker env variables are set correctly (default)" {
run docker exec mail_pop3 /bin/sh -c "grep '\$sa_tag_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 2.0'"
assert_success
run docker exec mail_pop3 /bin/sh -c "grep '\$sa_tag2_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 6.31'"
assert_success
run docker exec mail_pop3 /bin/sh -c "grep '\$sa_kill_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 6.31'"
assert_success
run docker exec mail_pop3 /bin/sh -c "grep '\$sa_spam_subject_tag' /etc/amavis/conf.d/20-debian_defaults | grep '= .\*\*\*SPAM\*\*\* .'"
assert_success
}
#
# system
#
@test "checking system: /var/log/mail/mail.log is error free" {
run docker exec mail_pop3 grep 'non-null host address bits in' /var/log/mail/mail.log
assert_failure
run docker exec mail_pop3 grep ': error:' /var/log/mail/mail.log
assert_failure
}
#
# sieve
#
@test "checking manage sieve: disabled per default" {
run docker exec mail_pop3 /bin/bash -c "nc -z 0.0.0.0 4190"
assert_failure
}
#
# PERMIT_DOCKER mynetworks
#
@test "checking PERMIT_DOCKER: my network value" {
run docker exec mail_pop3 /bin/sh -c "postconf | grep '^mynetworks =' | egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}/32'"
assert_success
}

View file

@ -0,0 +1,80 @@
load 'test_helper/common'
# Test case
# ---------
# POSTFIX_INET_PROTOCOLS value is set
@test "checking postfix: inet default" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . )
docker run -d --name mail_postfix_inet_default \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-h mail.my-domain.com -t "${NAME}"
teardown() { docker rm -f mail_postfix_inet_default; }
wait_for_finished_setup_in_container mail_postfix_inet_default
run docker exec mail_postfix_inet_default postconf inet_protocols
assert_output "inet_protocols = all"
assert_success
}
@test "checking postfix: inet all" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . )
docker run -d --name mail_postfix_inet_all \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e POSTFIX_INET_PROTOCOLS=all \
-h mail.my-domain.com -t "${NAME}"
teardown() { docker rm -f mail_postfix_inet_all; }
wait_for_finished_setup_in_container mail_postfix_inet_all
run docker exec mail_postfix_inet_all postconf inet_protocols
assert_output "inet_protocols = all"
assert_success
}
@test "checking postfix: inet ipv4" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . )
docker run -d --name mail_postfix_inet_ipv4 \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e POSTFIX_INET_PROTOCOLS=ipv4 \
-h mail.my-domain.com -t "${NAME}"
teardown() { docker rm -f mail_postfix_inet_ipv4; }
wait_for_finished_setup_in_container mail_postfix_inet_ipv4
run docker exec mail_postfix_inet_ipv4 postconf inet_protocols
assert_output "inet_protocols = ipv4"
assert_success
}
@test "checking postfix: inet ipv6" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . )
docker run -d --name mail_postfix_inet_ipv6 \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e POSTFIX_INET_PROTOCOLS=ipv6 \
-h mail.my-domain.com -t "${NAME}"
teardown() { docker rm -f mail_postfix_inet_ipv6; }
wait_for_finished_setup_in_container mail_postfix_inet_ipv6
run docker exec mail_postfix_inet_ipv6 postconf inet_protocols
assert_output "inet_protocols = ipv6"
assert_success
}

View file

@ -0,0 +1,52 @@
load 'test_helper/common'
setup() {
# Getting mail container IP
MAIL_POSTSCREEN_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' mail_postscreen)
}
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_postscreen \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e POSTSCREEN_ACTION=enforce \
--cap-add=NET_ADMIN \
-h mail.my-domain.com -t "${NAME}"
docker run --name mail_postscreen_sender \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-d "${NAME}" \
tail -f /var/log/faillog
wait_for_smtp_port_in_container mail_postscreen
}
teardown_file() {
docker rm -f mail_postscreen mail_postscreen_sender
}
@test "checking postscreen: talk too fast" {
docker exec mail_postscreen_sender /bin/sh -c "nc ${MAIL_POSTSCREEN_IP} 25 < /tmp/docker-mailserver-test/auth/smtp-auth-login.txt"
repeat_until_success_or_timeout 10 run docker exec mail_postscreen grep 'COMMAND PIPELINING' /var/log/mail/mail.log
assert_success
}
@test "checking postscreen: positive test (respecting postscreen_greet_wait time and talking in turn)" {
for _ in {1,2}; do
# shellcheck disable=SC1004
docker exec mail_postscreen_sender /bin/bash -c \
'exec 3<>/dev/tcp/'"${MAIL_POSTSCREEN_IP}"'/25 && \
while IFS= read -r cmd; do \
head -1 <&3; \
[[ ${cmd} == "EHLO"* ]] && sleep 6; \
echo ${cmd} >&3; \
done < "/tmp/docker-mailserver-test/auth/smtp-auth-login.txt"'
done
repeat_until_success_or_timeout 10 run docker exec mail_postscreen grep 'PASS NEW ' /var/log/mail/mail.log
assert_success
}

View file

@ -0,0 +1,39 @@
load 'test_helper/common'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_privacy \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_MANAGESIEVE=1 \
-e PERMIT_DOCKER=host \
-h mail.my-domain.com \
-e SSL_TYPE='snakeoil' \
--tty \
"${NAME}" # Image name
wait_for_amavis_port_in_container mail_privacy
wait_for_smtp_port_in_container mail_privacy
}
function teardown_file() {
docker rm -f mail_privacy
}
# What this test should cover: https://github.com/docker-mailserver/docker-mailserver/issues/681
@test "checking postfix: remove privacy details of the sender" {
docker exec mail_privacy /bin/sh -c "openssl s_client -quiet -starttls smtp -connect 0.0.0.0:587 < /tmp/docker-mailserver-test/email-templates/send-privacy-email.txt"
# shellcheck disable=SC2016
repeat_until_success_or_timeout 120 docker exec mail_privacy /bin/bash -c '[[ $(ls /var/mail/localhost.localdomain/user1/new | wc -l) -eq 1 ]]'
docker logs mail_privacy
run docker exec mail_privacy /bin/sh -c "ls /var/mail/localhost.localdomain/user1/new | wc -l"
assert_success
assert_output 1
run docker exec mail_privacy /bin/sh -c 'grep -rE "^User-Agent:" /var/mail/localhost.localdomain/user1/new | wc -l'
assert_success
assert_output 0
}

View file

@ -0,0 +1,43 @@
load 'test_helper/common'
# Test case
# ---------
# When ENABLE_QUOTAS is explicitly disabled (ENABLE_QUOTAS=0), dovecot quota must not be enabled.
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_no_quotas \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_QUOTAS=0 \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_no_quotas
}
function teardown_file() {
docker rm -f mail_no_quotas
}
@test "checking dovecot: (ENABLE_QUOTAS=0) quota plugin is disabled" {
run docker exec mail_no_quotas /bin/sh -c "grep '\$mail_plugins quota' /etc/dovecot/conf.d/10-mail.conf"
assert_failure
run docker exec mail_no_quotas /bin/sh -c "grep '\$mail_plugins imap_quota' /etc/dovecot/conf.d/20-imap.conf"
assert_failure
run docker exec mail_no_quotas ls /etc/dovecot/conf.d/90-quota.conf
assert_failure
run docker exec mail_no_quotas ls /etc/dovecot/conf.d/90-quota.conf.disab
assert_success
}
@test "checking postfix: (ENABLE_QUOTAS=0) dovecot quota absent in postconf" {
run docker exec mail_no_quotas /bin/bash -c "postconf | grep 'check_policy_service inet:localhost:65265'"
assert_failure
}

View file

@ -0,0 +1,68 @@
load 'test_helper/common'
function setup_file() {
docker run --rm -d --name mail_smtponly \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e SMTP_ONLY=1 \
-e PERMIT_DOCKER=network \
-e OVERRIDE_HOSTNAME=mail.my-domain.com \
-t "${NAME}"
wait_for_finished_setup_in_container mail_smtponly
}
function teardown_file() {
docker rm -f mail_smtponly
}
#
# configuration checks
#
@test "checking configuration: hostname/domainname override" {
run docker exec mail_smtponly /bin/bash -c "cat /etc/mailname | grep my-domain.com"
assert_success
}
#
# imap
#
@test "checking process: dovecot imaplogin (disabled using SMTP_ONLY)" {
run docker exec mail_smtponly /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/dovecot'"
assert_failure
}
@test "checking configuration: dovecot quota absent in postconf (disabled using SMTP_ONLY)" {
run docker exec mail_smtponly /bin/bash -c "postconf | grep 'check_policy_service inet:localhost:65265'"
assert_failure
}
#
# smtp
#
@test "checking smtp_only: mail send should work" {
run docker exec mail_smtponly /bin/sh -c "postconf smtp_host_lookup=no"
assert_success
run docker exec mail_smtponly /bin/sh -c "/etc/init.d/postfix reload"
assert_success
wait_for_smtp_port_in_container mail_smtponly
run docker exec mail_smtponly /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/smtp-only.txt"
assert_success
run docker exec mail_smtponly /bin/sh -c 'grep -cE "to=<user2\@external.tld>.*status\=sent" /var/log/mail/mail.log'
[[ ${status} -ge 0 ]]
}
#
# PERMIT_DOCKER=network
#
@test "checking PERMIT_DOCKER=network: opendmarc/opendkim config" {
run docker exec mail_smtponly /bin/sh -c "cat /etc/opendmarc/ignore.hosts | grep '172.16.0.0/12'"
assert_success
run docker exec mail_smtponly /bin/sh -c "cat /etc/opendkim/TrustedHosts | grep '172.16.0.0/12'"
assert_success
}

View file

@ -0,0 +1,67 @@
load 'test_helper/common'
# Test case
# ---------
# When SPAMASSASSIN_SPAM_TO_INBOX=1, spam messages must be delivered and eventually (MOVE_SPAM_TO_JUNK=1) moved to the Junk folder.
@test "checking amavis: spam message is delivered and moved to the Junk folder (MOVE_SPAM_TO_JUNK=1)" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_spam_moved_junk)
docker run -d --name mail_spam_moved_junk \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_SPAMASSASSIN=1 \
-e MOVE_SPAM_TO_JUNK=1 \
-e PERMIT_DOCKER=container \
-e SA_SPAM_SUBJECT="SPAM: " \
-e SPAMASSASSIN_SPAM_TO_INBOX=1 \
-h mail.my-domain.com -t "${NAME}"
teardown() { docker rm -f mail_spam_moved_junk; }
wait_for_smtp_port_in_container mail_spam_moved_junk
# send a spam message
run docker exec mail_spam_moved_junk /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
assert_success
# message will be added to a queue with varying delay until amavis receives it
run repeat_until_success_or_timeout 60 sh -c "docker logs mail_spam_moved_junk | grep 'Passed SPAM {RelayedTaggedInbound,Quarantined}'"
assert_success
# spam moved to Junk folder
run repeat_until_success_or_timeout 20 sh -c "docker exec mail_spam_moved_junk sh -c 'grep \"Subject: SPAM: \" /var/mail/localhost.localdomain/user1/.Junk/new/ -R'"
assert_success
}
@test "checking amavis: spam message is delivered to INBOX (MOVE_SPAM_TO_JUNK=0)" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_spam_moved_new)
docker run -d --name mail_spam_moved_new \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_SPAMASSASSIN=1 \
-e MOVE_SPAM_TO_JUNK=0 \
-e PERMIT_DOCKER=container \
-e SA_SPAM_SUBJECT="SPAM: " \
-e SPAMASSASSIN_SPAM_TO_INBOX=1 \
-h mail.my-domain.com -t "${NAME}"
teardown() { docker rm -f mail_spam_moved_new; }
wait_for_smtp_port_in_container mail_spam_moved_new
# send a spam message
run docker exec mail_spam_moved_new /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
assert_success
# message will be added to a queue with varying delay until amavis receives it
run repeat_until_success_or_timeout 60 sh -c "docker logs mail_spam_moved_new | grep 'Passed SPAM {RelayedTaggedInbound,Quarantined}'"
assert_success
# spam moved to INBOX
run repeat_until_success_or_timeout 20 sh -c "docker exec mail_spam_moved_new sh -c 'grep \"Subject: SPAM: \" /var/mail/localhost.localdomain/user1/new/ -R'"
assert_success
}

View file

@ -0,0 +1,41 @@
load 'test_helper/common'
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_special_use_folders \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e SASL_PASSWD="external-domain.com username:password" \
-e ENABLE_CLAMAV=0 \
-e ENABLE_SPAMASSASSIN=0 \
-e PERMIT_DOCKER=host \
-h mail.my-domain.com -t "${NAME}"
wait_for_smtp_port_in_container mail_special_use_folders
}
teardown_file() {
docker rm -f mail_special_use_folders
}
@test "checking normal delivery" {
run docker exec mail_special_use_folders /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
assert_success
# shellcheck disable=SC2016
repeat_until_success_or_timeout 30 docker exec mail_special_use_folders /bin/sh -c '[ $(ls /var/mail/localhost.localdomain/user1/new | wc -l) -eq 1 ]'
}
@test "checking special-use folders not yet created" {
run docker exec mail_special_use_folders /bin/bash -c "ls -A /var/mail/localhost.localdomain/user1 | grep -E '.Drafts|.Sent|.Trash' | wc -l"
assert_success
assert_output 0
}
@test "checking special-use folders available in IMAP" {
run docker exec mail_special_use_folders /bin/sh -c "nc -w 8 0.0.0.0 143 < /tmp/docker-mailserver-test/nc_templates/imap_special_use_folders.txt | grep -E 'Drafts|Junk|Trash|Sent' | wc -l"
assert_success
assert_output 4
}

View file

@ -0,0 +1,311 @@
load 'test_helper/common'
load 'test_helper/tls'
# Globals referenced from `test_helper/common`:
# TEST_NAME TEST_FQDN TEST_TMP_CONFIG
# Requires maintenance (TODO): Yes
# Can run tests in parallel?: No
# Not parallelize friendly when TEST_NAME is static,
# presently name of test file: `mail_ssl_letsencrypt`.
#
# Also shares a common TEST_TMP_CONFIG local folder,
# Instead of individual PRIVATE_CONFIG copies.
# For this test that is a non-issue, unless run in parallel.
# Applies to all tests:
function setup_file() {
init_with_defaults
# Override default to match the hostname we want to test against instead:
export TEST_FQDN='mail.example.test'
# Prepare certificates in the letsencrypt supported file structure:
# Note Certbot uses `privkey.pem`.
# `fullchain.pem` is currently what's detected, but we're actually providing the equivalent of `cert.pem` here.
# TODO: Verify format/structure is supported for nginx-proxy + acme-companion (uses `acme.sh` to provision).
# `mail.example.test` (Only this FQDN is supported by this certificate):
_copy_to_letsencrypt_storage 'example.test/with_ca/ecdsa/cert.ecdsa.pem' 'mail.example.test/fullchain.pem'
_copy_to_letsencrypt_storage 'example.test/with_ca/ecdsa/key.ecdsa.pem' "mail.example.test/privkey.pem"
# `example.test` (Only this FQDN is supported by this certificate):
_copy_to_letsencrypt_storage 'example.test/with_ca/ecdsa/cert.rsa.pem' 'example.test/fullchain.pem'
_copy_to_letsencrypt_storage 'example.test/with_ca/ecdsa/key.rsa.pem' 'example.test/privkey.pem'
}
# Not used
# function teardown_file() {
# }
function teardown() {
docker rm -f "${TEST_NAME}"
}
# Should detect and choose the cert for FQDN `mail.example.test` (HOSTNAME):
@test "ssl(letsencrypt): Should default to HOSTNAME (mail.example.test)" {
local TARGET_DOMAIN='mail.example.test'
local TEST_DOCKER_ARGS=(
--volume "${TEST_TMP_CONFIG}/letsencrypt/${TARGET_DOMAIN}/:/etc/letsencrypt/live/${TARGET_DOMAIN}/:ro"
--env PERMIT_DOCKER='container'
--env SSL_TYPE='letsencrypt'
)
common_container_setup 'TEST_DOCKER_ARGS'
#test hostname has certificate files
_should_have_valid_config "${TARGET_DOMAIN}" 'privkey.pem' 'fullchain.pem'
_should_succesfully_negotiate_tls "${TARGET_DOMAIN}"
_should_not_support_fqdn_in_cert 'example.test'
}
# Should detect and choose cert for FQDN `example.test` (DOMAINNAME),
# as fallback when no cert for FQDN `mail.example.test` (HOSTNAME) exists:
@test "ssl(letsencrypt): Should fallback to DOMAINNAME (example.test)" {
local TARGET_DOMAIN='example.test'
local TEST_DOCKER_ARGS=(
--volume "${TEST_TMP_CONFIG}/letsencrypt/${TARGET_DOMAIN}/:/etc/letsencrypt/live/${TARGET_DOMAIN}/:ro"
--env PERMIT_DOCKER='container'
--env SSL_TYPE='letsencrypt'
)
common_container_setup 'TEST_DOCKER_ARGS'
#test domain has certificate files
_should_have_valid_config "${TARGET_DOMAIN}" 'privkey.pem' 'fullchain.pem'
_should_succesfully_negotiate_tls "${TARGET_DOMAIN}"
_should_not_support_fqdn_in_cert 'mail.example.test'
}
# When using `acme.json` (Traefik) - a wildcard cert `*.example.test` (SSL_DOMAIN)
# should be extracted and be chosen over an existing FQDN `mail.example.test` (HOSTNAME):
#
# NOTE: Currently all of the `acme.json` configs have the FQDN match a SAN value,
# all Subject CN (`main` in acme.json) are `Smallstep Leaf` which is not an FQDN.
# While valid for that field, it does mean there is no test coverage against `main`.
@test "ssl(letsencrypt): Traefik 'acme.json' (*.example.test)" {
# This test group changes to certs signed with an RSA Root CA key,
# These certs all support both FQDNs: `mail.example.test` and `example.test`,
# Except for the wildcard cert `*.example.test`, which intentionally excluded `example.test` when created.
# We want to maintain the same FQDN (mail.example.test) between the _acme_ecdsa and _acme_rsa tests.
local LOCAL_BASE_PATH="${PWD}/test/test-files/ssl/example.test/with_ca/rsa"
# Change default Root CA cert used for verifying chain of trust with openssl:
# shellcheck disable=SC2034
local TEST_CA_CERT="${TEST_FILES_CONTAINER_PATH}/ssl/example.test/with_ca/rsa/ca-cert.rsa.pem"
function _prepare() {
# Default `acme.json` for _acme_ecdsa test:
cp "${LOCAL_BASE_PATH}/ecdsa.acme.json" "${TEST_TMP_CONFIG}/letsencrypt/acme.json"
# TODO: Provision wildcard certs via Traefik to inspect if `example.test` non-wildcard is also added to the cert.
# shellcheck disable=SC2034
local TEST_DOCKER_ARGS=(
--volume "${TEST_TMP_CONFIG}/letsencrypt/acme.json:/etc/letsencrypt/acme.json:ro"
--env LOG_LEVEL='trace'
--env PERMIT_DOCKER='container'
--env SSL_DOMAIN='*.example.test'
--env SSL_TYPE='letsencrypt'
)
common_container_setup 'TEST_DOCKER_ARGS'
wait_for_service "${TEST_NAME}" 'changedetector'
# Wait until the changedetector service startup delay is over:
repeat_until_success_or_timeout 20 sh -c "$(_get_service_logs 'changedetector') | grep 'Changedetector is ready'"
}
# Test `acme.json` extraction works at container startup:
# It should have already extracted `mail.example.test` from the original mounted `acme.json`.
function _acme_ecdsa() {
_should_have_succeeded_at_extraction 'mail.example.test'
# SSL_DOMAIN set as ENV, but startup should not have match in `acme.json`:
_should_have_failed_at_extraction '*.example.test' 'mailserver'
_should_have_valid_config 'mail.example.test' 'key.pem' 'fullchain.pem'
local ECDSA_KEY_PATH="${LOCAL_BASE_PATH}/key.ecdsa.pem"
local ECDSA_CERT_PATH="${LOCAL_BASE_PATH}/cert.ecdsa.pem"
_should_have_expected_files 'mail.example.test' "${ECDSA_KEY_PATH}" "${ECDSA_CERT_PATH}"
}
# Test `acme.json` extraction is triggered via change detection:
# The updated `acme.json` roughly emulates a renewal, but changes from an ECDSA cert to an RSA one.
# It should replace the cert files in the existing `letsencrypt/live/mail.example.test/` folder.
function _acme_rsa() {
_should_extract_on_changes 'mail.example.test' "${LOCAL_BASE_PATH}/rsa.acme.json"
_should_have_service_restart_count '1'
local RSA_KEY_PATH="${LOCAL_BASE_PATH}/key.rsa.pem"
local RSA_CERT_PATH="${LOCAL_BASE_PATH}/cert.rsa.pem"
_should_have_expected_files 'mail.example.test' "${RSA_KEY_PATH}" "${RSA_CERT_PATH}"
}
# Test that `acme.json` also works with wildcard certificates:
# Additionally tests that SSL_DOMAIN is prioritized when `letsencrypt/live/` already has a HOSTNAME dir available.
# Wildcard `*.example.test` should extract to `example.test/` in `letsencrypt/live/`:
function _acme_wildcard() {
_should_extract_on_changes 'example.test' "${LOCAL_BASE_PATH}/wildcard/rsa.acme.json"
_should_have_service_restart_count '2'
# As the FQDN has changed since startup, the Postfix + Dovecot configs should be updated:
_should_have_valid_config 'example.test' 'key.pem' 'fullchain.pem'
local WILDCARD_KEY_PATH="${LOCAL_BASE_PATH}/wildcard/key.rsa.pem"
local WILDCARD_CERT_PATH="${LOCAL_BASE_PATH}/wildcard/cert.rsa.pem"
_should_have_expected_files 'example.test' "${WILDCARD_KEY_PATH}" "${WILDCARD_CERT_PATH}"
# These two tests will confirm wildcard support is working, the supported SANs changed:
# Before (_acme_rsa cert): `DNS:example.test, DNS:mail.example.test`
# After (_acme_wildcard cert): `DNS:*.example.test`
# The difference in support is:
# - `example.test` should no longer be valid.
# - `mail.example.test` should remain valid, but also allow any other subdomain/hostname.
_should_succesfully_negotiate_tls 'mail.example.test'
_should_support_fqdn_in_cert 'fake.example.test'
_should_not_support_fqdn_in_cert 'example.test'
}
_prepare
# Unleash the `acme.json` tests!
# TODO: Extract methods to separate test cases.
_acme_ecdsa
_acme_rsa
_acme_wildcard
}
#
# Test Methods
#
# Check that Dovecot and Postfix are configured to use a cert for the expected FQDN:
function _should_have_valid_config() {
local EXPECTED_FQDN=${1}
local LE_KEY_PATH="/etc/letsencrypt/live/${EXPECTED_FQDN}/${2}"
local LE_CERT_PATH="/etc/letsencrypt/live/${EXPECTED_FQDN}/${3}"
_has_matching_line 'postconf' "smtpd_tls_chain_files = ${LE_KEY_PATH} ${LE_CERT_PATH}"
_has_matching_line 'doveconf' "ssl_cert = <${LE_CERT_PATH}"
# `-P` is required to prevent redacting secrets
_has_matching_line 'doveconf -P' "ssl_key = <${LE_KEY_PATH}"
}
# CMD ${1} run in container with output checked to match value of ${2}:
function _has_matching_line() {
run docker exec "${TEST_NAME}" sh -c "${1} | grep '${2}'"
assert_output "${2}"
}
#
# Traefik `acme.json` specific
#
# It should log success of extraction for the expected domain and restart Postfix.
function _should_have_succeeded_at_extraction() {
local EXPECTED_DOMAIN=${1}
local SERVICE=${2}
run $(_get_service_logs "${SERVICE}")
assert_output --partial "_extract_certs_from_acme | Certificate successfully extracted for '${EXPECTED_DOMAIN}'"
}
function _should_have_failed_at_extraction() {
local EXPECTED_DOMAIN=${1}
local SERVICE=${2}
run $(_get_service_logs "${SERVICE}")
assert_output --partial "_extract_certs_from_acme | Unable to find key and/or cert for '${EXPECTED_DOMAIN}' in '/etc/letsencrypt/acme.json'"
}
# Replace the mounted `acme.json` and wait to see if changes were detected.
function _should_extract_on_changes() {
local EXPECTED_DOMAIN=${1}
local ACME_JSON=${2}
cp "${ACME_JSON}" "${TEST_TMP_CONFIG}/letsencrypt/acme.json"
# Change detection takes a little over 5 seconds to complete (restart services)
sleep 10
# Expected log lines from the changedetector service:
run $(_get_service_logs 'changedetector')
assert_output --partial 'Change detected'
assert_output --partial "'/etc/letsencrypt/acme.json' has changed - extracting certificates"
assert_output --partial "_extract_certs_from_acme | Certificate successfully extracted for '${EXPECTED_DOMAIN}'"
assert_output --partial 'Restarting services due to detected changes'
assert_output --partial 'postfix: stopped'
assert_output --partial 'postfix: started'
assert_output --partial 'dovecot: stopped'
assert_output --partial 'dovecot: started'
}
# Ensure change detection is not mistakenly validating against previous change events:
function _should_have_service_restart_count() {
local NUM_RESTARTS=${1}
# Count how many times postfix was restarted by the `changedetector` service:
run docker exec "${TEST_NAME}" sh -c "grep -c 'postfix: started' /var/log/supervisor/changedetector.log"
assert_output "${NUM_RESTARTS}"
}
# Extracted cert files from `acme.json` have content matching the expected reference files:
function _should_have_expected_files() {
local LE_BASE_PATH="/etc/letsencrypt/live/${1}"
local LE_KEY_PATH="${LE_BASE_PATH}/key.pem"
local LE_CERT_PATH="${LE_BASE_PATH}/fullchain.pem"
local EXPECTED_KEY_PATH=${2}
local EXPECTED_CERT_PATH=${3}
_should_be_equal_in_content "${LE_KEY_PATH}" "${EXPECTED_KEY_PATH}"
_should_be_equal_in_content "${LE_CERT_PATH}" "${EXPECTED_CERT_PATH}"
}
#
# Misc
#
# Rename test certificate files to match the expected file structure for letsencrypt:
function _copy_to_letsencrypt_storage() {
local SRC=${1}
local DEST=${2}
local FQDN_DIR
FQDN_DIR=$(echo "${DEST}" | cut -d '/' -f1)
mkdir -p "${TEST_TMP_CONFIG}/letsencrypt/${FQDN_DIR}"
cp "${PWD}/test/test-files/ssl/${SRC}" "${TEST_TMP_CONFIG}/letsencrypt/${DEST}"
}
function _should_be_equal_in_content() {
local CONTAINER_PATH=${1}
local LOCAL_PATH=${2}
run docker exec "${TEST_NAME}" sh -c "cat ${CONTAINER_PATH}"
assert_output "$(cat "${LOCAL_PATH}")"
assert_success
}
function _get_service_logs() {
local SERVICE=${1:-'mailserver'}
local CMD_LOGS=(docker exec "${TEST_NAME}" "supervisorctl tail -2200 ${SERVICE}")
# As the `mailserver` service logs are not stored in a file but output to stdout/stderr,
# The `supervisorctl tail` command won't work; we must instead query via `docker logs`:
if [[ ${SERVICE} == 'mailserver' ]]
then
CMD_LOGS=(docker logs "${TEST_NAME}")
fi
echo "${CMD_LOGS[@]}"
}

View file

@ -0,0 +1,111 @@
#!/usr/bin/env bats
load 'test_helper/common'
function setup_file() {
# Internal copies made by `start-mailserver.sh`:
export PRIMARY_KEY='/etc/dms/tls/key'
export PRIMARY_CERT='/etc/dms/tls/cert'
export FALLBACK_KEY='/etc/dms/tls/fallback_key'
export FALLBACK_CERT='/etc/dms/tls/fallback_cert'
# Volume mounted certs:
export SSL_KEY_PATH='/config/ssl/key.ecdsa.pem'
export SSL_CERT_PATH='/config/ssl/cert.ecdsa.pem'
export SSL_ALT_KEY_PATH='/config/ssl/key.rsa.pem'
export SSL_ALT_CERT_PATH='/config/ssl/cert.rsa.pem'
local PRIVATE_CONFIG
export DOMAIN_SSL_MANUAL='example.test'
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_manual_ssl \
--volume "${PRIVATE_CONFIG}/:/tmp/docker-mailserver/" \
--volume "$(pwd)/test/test-files/ssl/${DOMAIN_SSL_MANUAL}/with_ca/ecdsa/:/config/ssl/:ro" \
--env LOG_LEVEL='trace' \
--env SSL_TYPE='manual' \
--env TLS_LEVEL='modern' \
--env SSL_KEY_PATH="${SSL_KEY_PATH}" \
--env SSL_CERT_PATH="${SSL_CERT_PATH}" \
--env SSL_ALT_KEY_PATH="${SSL_ALT_KEY_PATH}" \
--env SSL_ALT_CERT_PATH="${SSL_ALT_CERT_PATH}" \
--hostname "mail.${DOMAIN_SSL_MANUAL}" \
--tty \
"${NAME}" # Image name
wait_for_finished_setup_in_container mail_manual_ssl
}
function teardown_file() {
docker rm -f mail_manual_ssl
}
@test "checking ssl: ENV vars provided are valid files" {
assert docker exec mail_manual_ssl [ -f "${SSL_CERT_PATH}" ]
assert docker exec mail_manual_ssl [ -f "${SSL_KEY_PATH}" ]
assert docker exec mail_manual_ssl [ -f "${SSL_ALT_CERT_PATH}" ]
assert docker exec mail_manual_ssl [ -f "${SSL_ALT_KEY_PATH}" ]
}
@test "checking ssl: manual configuration is correct" {
local DOVECOT_CONFIG_SSL='/etc/dovecot/conf.d/10-ssl.conf'
run docker exec mail_manual_ssl grep '^smtpd_tls_chain_files =' '/etc/postfix/main.cf'
assert_success
assert_output "smtpd_tls_chain_files = ${PRIMARY_KEY} ${PRIMARY_CERT} ${FALLBACK_KEY} ${FALLBACK_CERT}"
run docker exec mail_manual_ssl grep '^ssl_key =' "${DOVECOT_CONFIG_SSL}"
assert_success
assert_output "ssl_key = <${PRIMARY_KEY}"
run docker exec mail_manual_ssl grep '^ssl_cert =' "${DOVECOT_CONFIG_SSL}"
assert_success
assert_output "ssl_cert = <${PRIMARY_CERT}"
run docker exec mail_manual_ssl grep '^ssl_alt_key =' "${DOVECOT_CONFIG_SSL}"
assert_success
assert_output "ssl_alt_key = <${FALLBACK_KEY}"
run docker exec mail_manual_ssl grep '^ssl_alt_cert =' "${DOVECOT_CONFIG_SSL}"
assert_success
assert_output "ssl_alt_cert = <${FALLBACK_CERT}"
}
@test "checking ssl: manual configuration copied files correctly " {
run docker exec mail_manual_ssl cmp -s "${PRIMARY_KEY}" "${SSL_KEY_PATH}"
assert_success
run docker exec mail_manual_ssl cmp -s "${PRIMARY_CERT}" "${SSL_CERT_PATH}"
assert_success
# Fallback cert
run docker exec mail_manual_ssl cmp -s "${FALLBACK_KEY}" "${SSL_ALT_KEY_PATH}"
assert_success
run docker exec mail_manual_ssl cmp -s "${FALLBACK_CERT}" "${SSL_ALT_CERT_PATH}"
assert_success
}
@test "checking ssl: manual cert works correctly" {
wait_for_tcp_port_in_container 587 mail_manual_ssl
local TEST_COMMAND=(timeout 1 openssl s_client -connect mail.example.test:587 -starttls smtp)
local RESULT
# Should fail as a chain of trust is required to verify successfully:
RESULT=$(docker exec mail_manual_ssl "${TEST_COMMAND[@]}" | grep 'Verification error:')
assert_equal "${RESULT}" 'Verification error: unable to verify the first certificate'
# Provide the Root CA cert for successful verification:
local CA_CERT='/config/ssl/ca-cert.ecdsa.pem'
assert docker exec mail_manual_ssl [ -f "${CA_CERT}" ]
RESULT=$(docker exec mail_manual_ssl "${TEST_COMMAND[@]}" -CAfile "${CA_CERT}" | grep 'Verification: OK')
assert_equal "${RESULT}" 'Verification: OK'
}
@test "checking ssl: manual cert changes are picked up by check-for-changes" {
printf '%s' 'someThingsChangedHere' \
>>"$(pwd)/test/test-files/ssl/${DOMAIN_SSL_MANUAL}/with_ca/ecdsa/key.ecdsa.pem"
run timeout 15 docker exec mail_manual_ssl bash -c "tail -F /var/log/supervisor/changedetector.log | sed '/Manual certificates have changed/ q'"
assert_success
sed -i '/someThingsChangedHere/d' "$(pwd)/test/test-files/ssl/${DOMAIN_SSL_MANUAL}/with_ca/ecdsa/key.ecdsa.pem"
}

View file

@ -0,0 +1,29 @@
load 'test_helper/common'
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_time \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e TZ='Asia/Jakarta' \
-e LOG_LEVEL=debug \
-h mail.my-domain.com -t "${NAME}"
wait_for_smtp_port_in_container mail_time
}
teardown_file() {
docker rm -f mail_time
}
@test "checking time: setting the time with TZ works correctly" {
run docker exec mail_time cat /etc/timezone
assert_success
assert_output 'Asia/Jakarta'
run docker exec mail_time date '+%Z'
assert_success
assert_output 'WIB'
}

View file

@ -0,0 +1,127 @@
load 'test_helper/common'
# Test case
# ---------
# By default, this image is using audited FFDHE groups (https://github.com/docker-mailserver/docker-mailserver/pull/1463)
#
# This test case covers the described case against both boolean states for `ONE_DIR`.
#
# Description:
# 1. Verify that the file `ffdhe4096.pem` has not been modified (checksum verification).
# 2. Verify Postfix and Dovecot are using the default `ffdhe4096.pem` from Dockerfile build.
# 3. When custom DHE parameters are supplied by the user as `/tmp/docker-mailserver/dhparams.pem`:
# - Verify Postfix and Dovecot use the custom `custom-dhe-params.pem` (contents is actually `ffdhe2048.pem`).
# - A warning is raised about usage of potentially insecure parameters.
function teardown() {
docker rm -f mail_dhparams
}
function setup_file() {
# Delegated container setup to common_container_setup
# DRY - Explicit config changes between tests are more apparent this way.
# Global scope
# Copies all of `./test/config/` to specific directory for testing
# `${PRIVATE_CONFIG}` becomes `$(pwd)/test/duplicate_configs/<bats test filename>`
export PRIVATE_CONFIG
export DMS_ONE_DIR=1 # default
local DH_DEFAULT_PARAMS
export DH_DEFAULT_CHECKSUM
export DH_CUSTOM_PARAMS
export DH_CUSTOM_CHECKSUM
DH_DEFAULT_PARAMS="$(pwd)/target/shared/ffdhe4096.pem"
DH_DEFAULT_CHECKSUM=$(sha512sum "${DH_DEFAULT_PARAMS}" | awk '{print $1}')
DH_CUSTOM_PARAMS="$(pwd)/test/test-files/ssl/custom-dhe-params.pem"
DH_CUSTOM_CHECKSUM=$(sha512sum "${DH_CUSTOM_PARAMS}" | awk '{print $1}')
}
# Not used
# function teardown_file() {
# }
@test "testing tls: DH Parameters - Verify integrity of Default (ffdhe4096)" {
# Reference used (22/04/2020):
# https://english.ncsc.nl/publications/publications/2019/juni/01/it-security-guidelines-for-transport-layer-security-tls
run echo "${DH_DEFAULT_CHECKSUM}"
refute_output '' # checksum must not be empty
# Verify the FFDHE params file has not been modified (equivalent to `target/shared/ffdhe4096.pem.sha512sum`):
local DH_MOZILLA_CHECKSUM
DH_MOZILLA_CHECKSUM=$(curl https://ssl-config.mozilla.org/ffdhe4096.txt -s | sha512sum | awk '{print $1}')
assert_equal "${DH_DEFAULT_CHECKSUM}" "${DH_MOZILLA_CHECKSUM}"
}
@test "testing tls: DH Parameters - Default [ONE_DIR=0]" {
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_dhparams_default_0)
DMS_ONE_DIR=0
common_container_setup
should_have_valid_checksum "${DH_DEFAULT_CHECKSUM}"
}
@test "testing tls: DH Parameters - Default [ONE_DIR=1]" {
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_dhparams_default_1)
common_container_setup
should_have_valid_checksum "${DH_DEFAULT_CHECKSUM}"
}
@test "testing tls: DH Parameters - Custom [ONE_DIR=0]" {
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_dhparams_custom_0)
# shellcheck disable=SC2030
DMS_ONE_DIR=0
cp "${DH_CUSTOM_PARAMS}" "${PRIVATE_CONFIG}/dhparams.pem"
common_container_setup
should_have_valid_checksum "${DH_CUSTOM_CHECKSUM}"
should_emit_warning
}
@test "testing tls: DH Parameters - Custom [ONE_DIR=1]" {
# shellcheck disable=SC2030
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_dhparams_custom_1)
cp "${DH_CUSTOM_PARAMS}" "${PRIVATE_CONFIG}/dhparams.pem"
common_container_setup
should_have_valid_checksum "${DH_CUSTOM_CHECKSUM}"
should_emit_warning
}
function common_container_setup() {
# shellcheck disable=SC2031
docker run -d --name mail_dhparams \
-v "${PRIVATE_CONFIG}:/tmp/docker-mailserver" \
-v "$(pwd)/test/test-files:/tmp/docker-mailserver-test:ro" \
-e ONE_DIR="${DMS_ONE_DIR}" \
-h mail.my-domain.com \
--tty \
"${NAME}"
wait_for_finished_setup_in_container mail_dhparams
}
# Ensures the docker image services (Postfix and Dovecot) have the intended DH files
function should_have_valid_checksum() {
local DH_CHECKSUM=$1
local DH_CHECKSUM_DOVECOT
DH_CHECKSUM_DOVECOT=$(docker exec mail_dhparams sha512sum /etc/dovecot/dh.pem | awk '{print $1}')
assert_equal "${DH_CHECKSUM_DOVECOT}" "${DH_CHECKSUM}"
local DH_CHECKSUM_POSTFIX
DH_CHECKSUM_POSTFIX=$(docker exec mail_dhparams sha512sum /etc/postfix/dhparams.pem | awk '{print $1}')
assert_equal "${DH_CHECKSUM_POSTFIX}" "${DH_CHECKSUM}"
}
function should_emit_warning() {
run sh -c "docker logs mail_dhparams | grep 'Using self-generated dhparams is considered insecure.'"
assert_success
}

View file

@ -0,0 +1,65 @@
load 'test_helper/common'
function setup() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_undef_spam_subject \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_SPAMASSASSIN=1 \
-e SA_SPAM_SUBJECT="undef" \
--hostname mail.my-domain.com \
--tty \
"${NAME}"
CONTAINER='mail_undef_spam_subject_2'
PRIVATE_CONFIG=$(duplicate_config_for_container . "${CONTAINER}")
docker run -d \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-v "$(pwd)/test/onedir":/var/mail-state \
-e ENABLE_CLAMAV=1 \
-e SPOOF_PROTECTION=1 \
-e ENABLE_SPAMASSASSIN=1 \
-e REPORT_RECIPIENT=user1@localhost.localdomain \
-e REPORT_SENDER=report1@mail.my-domain.com \
-e SA_TAG=-5.0 \
-e SA_TAG2=2.0 \
-e SA_KILL=3.0 \
-e SA_SPAM_SUBJECT="SPAM: " \
-e VIRUSMAILS_DELETE_DELAY=7 \
-e ENABLE_SRS=1 \
-e SASL_PASSWD="external-domain.com username:password" \
-e ENABLE_MANAGESIEVE=1 \
-e PERMIT_DOCKER=host \
--name "${CONTAINER}" \
--hostname mail.my-domain.com \
--tty \
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)" \
"${NAME}"
wait_for_finished_setup_in_container mail_undef_spam_subject
wait_for_finished_setup_in_container "${CONTAINER}"
}
function teardown() {
docker rm -f mail_undef_spam_subject "${CONTAINER}"
}
@test "checking spamassassin: docker env variables are set correctly (custom)" {
run docker exec "${CONTAINER}" /bin/sh -c "grep '\$sa_tag_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= -5.0'"
assert_success
run docker exec "${CONTAINER}" /bin/sh -c "grep '\$sa_tag2_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 2.0'"
assert_success
run docker exec "${CONTAINER}" /bin/sh -c "grep '\$sa_kill_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 3.0'"
assert_success
run docker exec "${CONTAINER}" /bin/sh -c "grep '\$sa_spam_subject_tag' /etc/amavis/conf.d/20-debian_defaults | grep '= .SPAM: .'"
assert_success
run docker exec mail_undef_spam_subject /bin/sh -c "grep '\$sa_spam_subject_tag' /etc/amavis/conf.d/20-debian_defaults | grep '= undef'"
assert_success
}

View file

@ -0,0 +1,49 @@
load 'test_helper/common'
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_with_imap \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_SASLAUTHD=1 \
-e POSTMASTER_ADDRESS=postmaster@localhost.localdomain \
-e SASLAUTHD_MECH_OPTIONS=127.0.0.1 \
-e SASLAUTHD_MECHANISMS=rimap \
-e PERMIT_DOCKER=container \
-h mail.my-domain.com -t "${NAME}"
wait_for_smtp_port_in_container mail_with_imap
}
teardown_file() {
docker rm -f mail_with_imap
}
#
# RIMAP
#
# dovecot
@test "checking dovecot: ldap rimap connection and authentication works" {
run docker exec mail_with_imap /bin/sh -c "nc -w 1 0.0.0.0 143 < /tmp/docker-mailserver-test/auth/imap-auth.txt"
assert_success
}
# saslauthd
@test "checking saslauthd: sasl rimap authentication works" {
run docker exec mail_with_imap bash -c "testsaslauthd -u user1@localhost.localdomain -p mypassword"
assert_success
}
@test "checking saslauthd: rimap smtp authentication" {
run docker exec mail_with_imap /bin/sh -c "nc -w 5 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/smtp-auth-login.txt | grep 'Authentication successful'"
assert_success
}
# master account
@test "checking dovecot: master account can login" {
run docker exec mail_with_imap bash -c "testsaslauthd -u user1@localhost.localdomain*masterusername -p masterpassword"
assert_success
}

View file

@ -0,0 +1,258 @@
load 'test_helper/common'
function setup_file() {
pushd test/docker-openldap/ || return 1
docker build -f Dockerfile -t ldap --no-cache .
popd || return 1
export DOMAIN='my-domain.com'
export FQDN_MAIL="mail.${DOMAIN}"
export FQDN_LDAP="ldap.${DOMAIN}"
export FQDN_LOCALHOST_A='localhost.localdomain'
export FQDN_LOCALHOST_B='localhost.otherdomain'
export DMS_TEST_NETWORK='test-network-ldap'
# NOTE: If the network already exists, test will fail to start.
docker network create "${DMS_TEST_NETWORK}"
docker run -d --name ldap_for_mail \
--env LDAP_DOMAIN="${FQDN_LOCALHOST_A}" \
--network "${DMS_TEST_NETWORK}" \
--network-alias 'ldap' \
--hostname "${FQDN_LDAP}" \
--tty \
ldap # Image name
# _setup_ldap uses configomat with .ext files and ENV vars like DOVECOT_TLS with a prefix (eg DOVECOT_ or LDAP_)
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_with_ldap \
-v "${PRIVATE_CONFIG}:/tmp/docker-mailserver" \
-v "$(pwd)/test/test-files:/tmp/docker-mailserver-test:ro" \
-e DOVECOT_PASS_FILTER="(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))" \
-e DOVECOT_TLS=no \
-e DOVECOT_USER_FILTER="(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))" \
-e ACCOUNT_PROVISIONER=LDAP \
-e PFLOGSUMM_TRIGGER=logrotate \
-e ENABLE_SASLAUTHD=1 \
-e LDAP_BIND_DN=cn=admin,dc=localhost,dc=localdomain \
-e LDAP_BIND_PW=admin \
-e LDAP_QUERY_FILTER_ALIAS="(|(&(mailAlias=%s)(objectClass=PostfixBookMailForward))(&(mailAlias=%s)(objectClass=PostfixBookMailAccount)(mailEnabled=TRUE)))" \
-e LDAP_QUERY_FILTER_DOMAIN="(|(&(mail=*@%s)(objectClass=PostfixBookMailAccount)(mailEnabled=TRUE))(&(mailGroupMember=*@%s)(objectClass=PostfixBookMailAccount)(mailEnabled=TRUE))(&(mailalias=*@%s)(objectClass=PostfixBookMailForward)))" \
-e LDAP_QUERY_FILTER_GROUP="(&(mailGroupMember=%s)(mailEnabled=TRUE))" \
-e LDAP_QUERY_FILTER_SENDERS="(|(&(mail=%s)(mailEnabled=TRUE))(&(mailGroupMember=%s)(mailEnabled=TRUE))(|(&(mailAlias=%s)(objectClass=PostfixBookMailForward))(&(mailAlias=%s)(objectClass=PostfixBookMailAccount)(mailEnabled=TRUE)))(uniqueIdentifier=some.user.id))" \
-e LDAP_QUERY_FILTER_USER="(&(mail=%s)(mailEnabled=TRUE))" \
-e LDAP_START_TLS=no \
-e LDAP_SEARCH_BASE=ou=people,dc=localhost,dc=localdomain \
-e LDAP_SERVER_HOST=ldap \
-e PERMIT_DOCKER=container \
-e POSTMASTER_ADDRESS="postmaster@${FQDN_LOCALHOST_A}" \
-e REPORT_RECIPIENT=1 \
-e SASLAUTHD_MECHANISMS=ldap \
-e SPOOF_PROTECTION=1 \
-e SSL_TYPE='snakeoil' \
--network "${DMS_TEST_NETWORK}" \
--hostname "${FQDN_MAIL}" \
--tty \
"${NAME}" # Image name
wait_for_smtp_port_in_container mail_with_ldap
}
function teardown_file() {
docker rm -f ldap_for_mail mail_with_ldap
docker network rm "${DMS_TEST_NETWORK}"
}
# processes
@test "checking process: saslauthd (saslauthd server enabled)" {
run docker exec mail_with_ldap /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/saslauthd'"
assert_success
}
# postfix
@test "checking postfix: ldap lookup works correctly" {
run docker exec mail_with_ldap /bin/sh -c "postmap -q some.user@${FQDN_LOCALHOST_A} ldap:/etc/postfix/ldap-users.cf"
assert_success
assert_output "some.user@${FQDN_LOCALHOST_A}"
run docker exec mail_with_ldap /bin/sh -c "postmap -q postmaster@${FQDN_LOCALHOST_A} ldap:/etc/postfix/ldap-aliases.cf"
assert_success
assert_output "some.user@${FQDN_LOCALHOST_A}"
run docker exec mail_with_ldap /bin/sh -c "postmap -q employees@${FQDN_LOCALHOST_A} ldap:/etc/postfix/ldap-groups.cf"
assert_success
assert_output "some.user@${FQDN_LOCALHOST_A}"
# Test of the user part of the domain is not the same as the uniqueIdentifier part in the ldap
run docker exec mail_with_ldap /bin/sh -c "postmap -q some.user.email@${FQDN_LOCALHOST_A} ldap:/etc/postfix/ldap-users.cf"
assert_success
assert_output "some.user.email@${FQDN_LOCALHOST_A}"
# Test email receiving from a other domain then the primary domain of the mailserver
run docker exec mail_with_ldap /bin/sh -c "postmap -q some.other.user@${FQDN_LOCALHOST_B} ldap:/etc/postfix/ldap-users.cf"
assert_success
assert_output "some.other.user@${FQDN_LOCALHOST_B}"
run docker exec mail_with_ldap /bin/sh -c "postmap -q postmaster@${FQDN_LOCALHOST_B} ldap:/etc/postfix/ldap-aliases.cf"
assert_success
assert_output "some.other.user@${FQDN_LOCALHOST_B}"
run docker exec mail_with_ldap /bin/sh -c "postmap -q employees@${FQDN_LOCALHOST_B} ldap:/etc/postfix/ldap-groups.cf"
assert_success
assert_output "some.other.user@${FQDN_LOCALHOST_B}"
}
@test "checking postfix: ldap custom config files copied" {
run docker exec mail_with_ldap /bin/sh -c "grep '# Testconfig for ldap integration' /etc/postfix/ldap-users.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep '# Testconfig for ldap integration' /etc/postfix/ldap-groups.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep '# Testconfig for ldap integration' /etc/postfix/ldap-aliases.cf"
assert_success
}
@test "checking postfix: ldap config overwrites success" {
run docker exec mail_with_ldap /bin/sh -c "grep 'server_host = ldap' /etc/postfix/ldap-users.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'start_tls = no' /etc/postfix/ldap-users.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'search_base = ou=people,dc=localhost,dc=localdomain' /etc/postfix/ldap-users.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'bind_dn = cn=admin,dc=localhost,dc=localdomain' /etc/postfix/ldap-users.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'server_host = ldap' /etc/postfix/ldap-groups.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'start_tls = no' /etc/postfix/ldap-groups.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'search_base = ou=people,dc=localhost,dc=localdomain' /etc/postfix/ldap-groups.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'bind_dn = cn=admin,dc=localhost,dc=localdomain' /etc/postfix/ldap-groups.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'server_host = ldap' /etc/postfix/ldap-aliases.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'start_tls = no' /etc/postfix/ldap-aliases.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'search_base = ou=people,dc=localhost,dc=localdomain' /etc/postfix/ldap-aliases.cf"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'bind_dn = cn=admin,dc=localhost,dc=localdomain' /etc/postfix/ldap-aliases.cf"
assert_success
}
# dovecot
@test "checking dovecot: ldap imap connection and authentication works" {
run docker exec mail_with_ldap /bin/sh -c "nc -w 1 0.0.0.0 143 < /tmp/docker-mailserver-test/auth/imap-ldap-auth.txt"
assert_success
}
@test "checking dovecot: ldap mail delivery works" {
run docker exec mail_with_ldap /bin/sh -c "sendmail -f user@external.tld some.user@${FQDN_LOCALHOST_A} < /tmp/docker-mailserver-test/email-templates/test-email.txt"
sleep 10
run docker exec mail_with_ldap /bin/sh -c "ls -A /var/mail/${FQDN_LOCALHOST_A}/some.user/new | wc -l"
assert_success
assert_output 1
}
@test "checking dovecot: ldap mail delivery works for a different domain then the mailserver" {
run docker exec mail_with_ldap /bin/sh -c "sendmail -f user@external.tld some.other.user@${FQDN_LOCALHOST_B} < /tmp/docker-mailserver-test/email-templates/test-email.txt"
sleep 10
run docker exec mail_with_ldap /bin/sh -c "ls -A /var/mail/${FQDN_LOCALHOST_A}/some.other.user/new | wc -l"
assert_success
assert_output 1
}
@test "checking dovecot: ldap config overwrites success" {
run docker exec mail_with_ldap /bin/sh -c "grep 'uris = ldap://ldap' /etc/dovecot/dovecot-ldap.conf.ext"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'tls = no' /etc/dovecot/dovecot-ldap.conf.ext"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'base = ou=people,dc=localhost,dc=localdomain' /etc/dovecot/dovecot-ldap.conf.ext"
assert_success
run docker exec mail_with_ldap /bin/sh -c "grep 'dn = cn=admin,dc=localhost,dc=localdomain' /etc/dovecot/dovecot-ldap.conf.ext"
assert_success
}
@test "checking dovecot: postmaster address" {
run docker exec mail_with_ldap /bin/sh -c "grep 'postmaster_address = postmaster@${FQDN_LOCALHOST_A}' /etc/dovecot/conf.d/15-lda.conf"
assert_success
}
@test "checking dovecot: quota plugin is disabled" {
run docker exec mail_with_ldap /bin/sh -c "grep '\$mail_plugins quota' /etc/dovecot/conf.d/10-mail.conf"
assert_failure
run docker exec mail_with_ldap /bin/sh -c "grep '\$mail_plugins imap_quota' /etc/dovecot/conf.d/20-imap.conf"
assert_failure
run docker exec mail_with_ldap ls /etc/dovecot/conf.d/90-quota.conf
assert_failure
run docker exec mail_with_ldap ls /etc/dovecot/conf.d/90-quota.conf.disab
assert_success
}
@test "checking postfix: dovecot quota absent in postconf" {
run docker exec mail_with_ldap /bin/bash -c "postconf | grep 'check_policy_service inet:localhost:65265'"
assert_failure
}
@test "checking spoofing (with LDAP): rejects sender forging" {
wait_for_smtp_port_in_container_to_respond mail_with_ldap
run docker exec mail_with_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/ldap-smtp-auth-spoofed.txt | grep 'Sender address rejected: not owned by user'"
assert_success
}
# ATTENTION: these tests must come after "checking dovecot: ldap mail delivery works" since they will deliver an email which skews the count in said test, leading to failure
@test "checking spoofing: accepts sending as alias (with LDAP)" {
run docker exec mail_with_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/ldap-smtp-auth-spoofed-alias.txt | grep 'End data with'"
assert_success
}
@test "checking spoofing: uses senders filter" {
run docker exec mail_with_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/ldap-smtp-auth-spoofed-sender-with-filter-exception.txt | grep 'End data with'"
assert_success
}
# saslauthd
@test "checking saslauthd: sasl ldap authentication works" {
run docker exec mail_with_ldap bash -c "testsaslauthd -u some.user -p secret"
assert_success
}
@test "checking saslauthd: ldap smtp authentication" {
run docker exec mail_with_ldap /bin/sh -c "nc -w 5 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/sasl-ldap-smtp-auth.txt | grep 'Authentication successful'"
assert_success
run docker exec mail_with_ldap /bin/sh -c "openssl s_client -quiet -connect 0.0.0.0:465 < /tmp/docker-mailserver-test/auth/sasl-ldap-smtp-auth.txt | grep 'Authentication successful'"
assert_success
run docker exec mail_with_ldap /bin/sh -c "openssl s_client -quiet -starttls smtp -connect 0.0.0.0:587 < /tmp/docker-mailserver-test/auth/sasl-ldap-smtp-auth.txt | grep 'Authentication successful'"
assert_success
}
#
# Pflogsumm delivery check
#
@test "checking pflogsum delivery" {
# checking default sender is correctly set when env variable not defined
run docker exec mail_with_ldap grep "mailserver-report@${FQDN_MAIL}" /etc/logrotate.d/maillog
assert_success
# checking default logrotation setup
run docker exec mail_with_ldap grep "weekly" /etc/logrotate.d/maillog
assert_success
}
#
# supervisor
#
@test "checking restart of process: saslauthd (saslauthd server enabled)" {
run docker exec mail_with_ldap /bin/bash -c "pkill saslauthd && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/saslauthd'"
assert_success
}

View file

@ -0,0 +1,30 @@
load 'test_helper/common'
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_with_mdbox_format \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e SASL_PASSWD="external-domain.com username:password" \
-e ENABLE_CLAMAV=0 \
-e ENABLE_SPAMASSASSIN=0 \
-e DOVECOT_MAILBOX_FORMAT=mdbox \
-e PERMIT_DOCKER=host \
-h mail.my-domain.com -t "${NAME}"
wait_for_smtp_port_in_container mail_with_mdbox_format
}
teardown_file() {
docker rm -f mail_with_mdbox_format
}
@test "checking dovecot mailbox format: mdbox file created" {
run docker exec mail_with_mdbox_format /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
assert_success
# shellcheck disable=SC2016
repeat_until_success_or_timeout 30 docker exec mail_with_mdbox_format /bin/sh -c '[ $(ls /var/mail/localhost.localdomain/user1/storage/m.1 | wc -l) -eq 1 ]'
}

View file

@ -0,0 +1,92 @@
load 'test_helper/common'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_with_postgrey \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_DNSBL=1 \
-e ENABLE_POSTGREY=1 \
-e PERMIT_DOCKER=container \
-e POSTGREY_AUTO_WHITELIST_CLIENTS=5 \
-e POSTGREY_DELAY=15 \
-e POSTGREY_MAX_AGE=35 \
-e POSTGREY_TEXT="Delayed by Postgrey" \
-h mail.my-domain.com -t "${NAME}"
# using postfix availability as start indicator, this might be insufficient for postgrey
wait_for_smtp_port_in_container mail_with_postgrey
}
function teardown_file() {
docker rm -f mail_with_postgrey
}
@test "checking postgrey: /etc/postfix/main.cf correctly edited" {
run docker exec mail_with_postgrey /bin/bash -c "grep -F 'zen.spamhaus.org=127.0.0.[2..11], check_policy_service inet:127.0.0.1:10023' /etc/postfix/main.cf | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: /etc/default/postgrey correctly edited and has the default values" {
run docker exec mail_with_postgrey /bin/bash -c "grep '^POSTGREY_OPTS=\"--inet=127.0.0.1:10023 --delay=15 --max-age=35 --auto-whitelist-clients=5\"$' /etc/default/postgrey | wc -l"
assert_success
assert_output 1
run docker exec mail_with_postgrey /bin/bash -c "grep '^POSTGREY_TEXT=\"Delayed by Postgrey\"$' /etc/default/postgrey | wc -l"
assert_success
assert_output 1
}
@test "checking process: postgrey (postgrey server enabled)" {
run docker exec mail_with_postgrey /bin/bash -c "ps aux --forest | grep -v grep | grep 'postgrey'"
assert_success
}
@test "checking postgrey: there should be a log entry about a new greylisted e-mail user@external.tld in /var/log/mail/mail.log" {
#editing the postfix config in order to ensure that postgrey handles the test e-mail. The other spam checks at smtpd_recipient_restrictions would interfere with it.
run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/permit_sasl_authenticated.*policyd-spf,$//g' /etc/postfix/main.cf"
run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/reject_unauth_pipelining.*reject_unknown_recipient_domain,$//g' /etc/postfix/main.cf"
run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/reject_rbl_client.*inet:127\.0\.0\.1:10023$//g' /etc/postfix/main.cf"
run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/smtpd_recipient_restrictions =/smtpd_recipient_restrictions = check_policy_service inet:127.0.0.1:10023/g' /etc/postfix/main.cf"
run docker exec mail_with_postgrey /bin/sh -c "/etc/init.d/postfix reload"
run docker exec mail_with_postgrey /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/postgrey.txt"
sleep 5 #ensure that the information has been written into the log
run docker exec mail_with_postgrey /bin/bash -c "grep -i 'action=greylist.*user@external\.tld' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: there should be a log entry about the retried and passed e-mail user@external.tld in /var/log/mail/mail.log" {
sleep 20 #wait 20 seconds so that postgrey would accept the message
run docker exec mail_with_postgrey /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/postgrey.txt"
sleep 8
run docker exec mail_with_postgrey /bin/sh -c "grep -i 'action=pass, reason=triplet found.*user@external\.tld' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: there should be a log entry about the whitelisted and passed e-mail user@whitelist.tld in /var/log/mail/mail.log" {
run docker exec mail_with_postgrey /bin/sh -c "nc -w 8 0.0.0.0 10023 < /tmp/docker-mailserver-test/nc_templates/postgrey_whitelist.txt"
run docker exec mail_with_postgrey /bin/sh -c "grep -i 'action=pass, reason=client whitelist' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: there should be a log entry about the whitelisted local and passed e-mail user@whitelistlocal.tld in /var/log/mail/mail.log" {
run docker exec mail_with_postgrey /bin/sh -c "nc -w 8 0.0.0.0 10023 < /tmp/docker-mailserver-test/nc_templates/postgrey_whitelist_local.txt"
run docker exec mail_with_postgrey /bin/sh -c "grep -i 'action=pass, reason=client whitelist' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: there should be a log entry about the whitelisted recipient user2@otherdomain.tld in /var/log/mail/mail.log" {
run docker exec mail_with_postgrey /bin/sh -c "nc -w 8 0.0.0.0 10023 < /tmp/docker-mailserver-test/nc_templates/postgrey_whitelist_recipients.txt"
run docker exec mail_with_postgrey /bin/sh -c "grep -i 'action=pass, reason=recipient whitelist' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}

View file

@ -0,0 +1,23 @@
load 'test_helper/common'
function setup() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
CONTAINER=$(docker run -d \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-h mail.my-domain.com -t "${NAME}")
# using postfix availability as start indicator, this might be insufficient for postgrey
wait_for_smtp_port_in_container "${CONTAINER}"
}
function teardown() {
docker rm -f "${CONTAINER}"
}
@test "checking process: postgrey (disabled in default configuration)" {
run docker exec "${CONTAINER}" /bin/bash -c "ps aux --forest | grep -v grep | grep 'postgrey'"
assert_failure
}

View file

@ -0,0 +1,81 @@
load 'test_helper/common'
function setup_file() {
# We use a temporary config directory since we'll be dynamically editing
# it with setup.sh.
tmp_confdir=$(mktemp -d /tmp/docker-mailserver-config-relay-hosts-XXXXX)
cp -a test/config/relay-hosts/* "${tmp_confdir}/"
docker run -d --name mail_with_relays \
-v "${tmp_confdir}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e RELAY_HOST=default.relay.com \
-e RELAY_PORT=2525 \
-e RELAY_USER=smtp_user \
-e RELAY_PASSWORD=smtp_password \
-e PERMIT_DOCKER=host \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_with_relays
}
function teardown_file() {
docker rm -f mail_with_relays
rm -rf "${tmp_confdir}"
}
@test "checking relay hosts: default mapping is added from env vars" {
run docker exec mail_with_relays grep -e domainone.tld /etc/postfix/relayhost_map
assert_output -e '^@domainone.tld[[:space:]]+\[default.relay.com\]:2525$'
}
@test "checking relay hosts: default mapping is added from env vars for virtual user entry" {
run docker exec mail_with_relays grep -e domain1.tld /etc/postfix/relayhost_map
assert_output -e '^@domain1.tld[[:space:]]+\[default.relay.com\]:2525$'
}
@test "checking relay hosts: default mapping is added from env vars for new user entry" {
run docker exec mail_with_relays grep -e domainzero.tld /etc/postfix/relayhost_map
assert_output ''
run ./setup.sh -c mail_with_relays email add user0@domainzero.tld password123
run_until_success_or_timeout 10 docker exec mail_with_relays grep -e domainzero.tld /etc/postfix/relayhost_map
assert_output -e '^@domainzero.tld[[:space:]]+\[default.relay.com\]:2525$'
}
@test "checking relay hosts: default mapping is added from env vars for new virtual user entry" {
run docker exec mail_with_relays grep -e domain2.tld /etc/postfix/relayhost_map
assert_output ''
run ./setup.sh -c mail_with_relays alias add user2@domain2.tld user2@domaintwo.tld
run_until_success_or_timeout 10 docker exec mail_with_relays grep -e domain2.tld /etc/postfix/relayhost_map
assert_output -e '^@domain2.tld[[:space:]]+\[default.relay.com\]:2525$'
}
@test "checking relay hosts: custom mapping is added from file" {
run docker exec mail_with_relays grep -e domaintwo.tld /etc/postfix/relayhost_map
assert_output -e '^@domaintwo.tld[[:space:]]+\[other.relay.com\]:587$'
}
@test "checking relay hosts: ignored domain is not added" {
run docker exec mail_with_relays grep -e domainthree.tld /etc/postfix/relayhost_map
assert_failure 1
assert_output ''
}
@test "checking relay hosts: sasl_passwd exists" {
run docker exec mail_with_relays [ -f /etc/postfix/sasl_passwd ]
assert_success
}
@test "checking relay hosts: auth entry is added" {
run docker exec mail_with_relays /bin/sh -c 'cat /etc/postfix/sasl_passwd | grep -e "^@domaintwo.tld\s\+smtp_user_2:smtp_password_2" | wc -l'
assert_success
assert_output 1
}
@test "checking relay hosts: default auth entry is added" {
run docker exec mail_with_relays /bin/sh -c 'cat /etc/postfix/sasl_passwd | grep -e "^\[default.relay.com\]:2525\s\+smtp_user:smtp_password" | wc -l'
assert_success
assert_output 1
}

View file

@ -0,0 +1,30 @@
load 'test_helper/common'
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_with_sdbox_format \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e SASL_PASSWD="external-domain.com username:password" \
-e ENABLE_CLAMAV=0 \
-e ENABLE_SPAMASSASSIN=0 \
-e DOVECOT_MAILBOX_FORMAT=sdbox \
-e PERMIT_DOCKER=host \
-h mail.my-domain.com -t "${NAME}"
wait_for_smtp_port_in_container mail_with_sdbox_format
}
teardown_file() {
docker rm -f mail_with_sdbox_format
}
@test "checking dovecot mailbox format: sdbox file created" {
run docker exec mail_with_sdbox_format /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
assert_success
# shellcheck disable=SC2016
repeat_until_success_or_timeout 30 docker exec mail_with_sdbox_format /bin/sh -c '[ $(ls /var/mail/localhost.localdomain/user1/mailboxes/INBOX/dbox-Mails/u.1 | wc -l) -eq 1 ]'
}

View file

@ -0,0 +1,144 @@
load 'test_helper/common'
function setup_file() {
# Fail early if the test image is already running:
assert_not_equal "$(docker ps | grep -o "${NAME}")" "${NAME}"
# Test may fail if an existing DMS container is running,
# Which can occur from a prior test failing before reaching `no_container.bats`
# and that failure not properly handling teardown.
}
@test "[No Existing Container] checking setup.sh: setup.sh alias list" {
mkdir -p ./test/alias/config && echo "test@example.org test@forward.com" > ./test/alias/config/postfix-virtual.cf
run ./setup.sh -p ./test/alias/config alias list
assert_success
}
@test "[No Existing Container] checking setup.sh: setup.sh alias add" {
mkdir -p ./test/alias/config && echo "" > ./test/alias/config/postfix-virtual.cf
./setup.sh -p ./test/alias/config alias add alias@example.com target1@forward.com
./setup.sh -p ./test/alias/config alias add alias@example.com target2@forward.com
sleep 5
run /bin/sh -c 'cat ./test/alias/config/postfix-virtual.cf | grep "alias@example.com target1@forward.com,target2@forward.com" | wc -l | grep 1'
assert_success
}
@test "[No Existing Container] checking setup.sh: setup.sh alias del" {
# start with a1 -> t1,t2 and a2 -> t1
mkdir -p ./test/alias/config && echo -e 'alias1@example.org target1@forward.com,target2@forward.com\nalias2@example.org target1@forward.com' > ./test/alias/config/postfix-virtual.cf
# we remove a1 -> t1 ==> a1 -> t2 and a2 -> t1
./setup.sh -p ./test/alias/config alias del alias1@example.org target1@forward.com
run grep "target1@forward.com" ./test/alias/config/postfix-virtual.cf
assert_output --regexp "^alias2@example.org +target1@forward.com$"
run grep "target2@forward.com" ./test/alias/config/postfix-virtual.cf
assert_output --regexp "^alias1@example.org +target2@forward.com$"
# we remove a1 -> t2 ==> a2 -> t1
./setup.sh -p ./test/alias/config alias del alias1@example.org target2@forward.com
run grep "alias1@example.org" ./test/alias/config/postfix-virtual.cf
assert_failure
run grep "alias2@example.org" ./test/alias/config/postfix-virtual.cf
assert_success
# we remove a2 -> t1 ==> empty
./setup.sh -p ./test/alias/config alias del alias2@example.org target1@forward.com
run grep "alias2@example.org" ./test/alias/config/postfix-virtual.cf
assert_failure
}
# quota
@test "[No Existing Container] checking setup.sh: setup.sh setquota" {
mkdir -p ./test/quota/config && echo "" > ./test/quota/config/dovecot-quotas.cf
run ./setup.sh -p ./test/quota/config email add quota_user@example.com test_password
run ./setup.sh -p ./test/quota/config email add quota_user2@example.com test_password
run ./setup.sh -p ./test/quota/config quota set quota_user@example.com 12M
assert_success
run ./setup.sh -p ./test/quota/config quota set 51M quota_user@example.com
assert_failure
run ./setup.sh -p ./test/quota/config quota set unknown@domain.com 150M
assert_failure
run ./setup.sh -p ./test/quota/config quota set quota_user2 51M
assert_failure
run /bin/sh -c 'cat ./test/quota/config/dovecot-quotas.cf | grep -E "^quota_user@example.com\:12M\$" | wc -l | grep 1'
assert_success
run ./setup.sh -p ./test/quota/config quota set quota_user@example.com 26M
assert_success
run /bin/sh -c 'cat ./test/quota/config/dovecot-quotas.cf | grep -E "^quota_user@example.com\:26M\$" | wc -l | grep 1'
assert_success
run grep "quota_user2@example.com" ./test/alias/config/dovecot-quotas.cf
assert_failure
}
@test "[No Existing Container] checking setup.sh: setup.sh delquota" {
mkdir -p ./test/quota/config && echo "" > ./test/quota/config/dovecot-quotas.cf
run ./setup.sh -p ./test/quota/config email add quota_user@example.com test_password
run ./setup.sh -p ./test/quota/config email add quota_user2@example.com test_password
run ./setup.sh -p ./test/quota/config quota set quota_user@example.com 12M
assert_success
run /bin/sh -c 'cat ./test/quota/config/dovecot-quotas.cf | grep -E "^quota_user@example.com\:12M\$" | wc -l | grep 1'
assert_success
run ./setup.sh -p ./test/quota/config quota del unknown@domain.com
assert_failure
run /bin/sh -c 'cat ./test/quota/config/dovecot-quotas.cf | grep -E "^quota_user@example.com\:12M\$" | wc -l | grep 1'
assert_success
run ./setup.sh -p ./test/quota/config quota del quota_user@example.com
assert_success
run grep "quota_user@example.com" ./test/alias/config/dovecot-quotas.cf
assert_failure
}
# debug
@test "[No Existing Container] checking setup.sh: setup.sh relay add-domain" {
mkdir -p ./test/relay/config && echo -n > ./test/relay/config/postfix-relaymap.cf
./setup.sh -p ./test/relay/config relay add-domain example1.org smtp.relay1.com 2525
./setup.sh -p ./test/relay/config relay add-domain example2.org smtp.relay2.com
./setup.sh -p ./test/relay/config relay add-domain example3.org smtp.relay3.com 2525
./setup.sh -p ./test/relay/config relay add-domain example3.org smtp.relay.com 587
# check adding
run /bin/sh -c 'cat ./test/relay/config/postfix-relaymap.cf | grep -e "^@example1.org\s\+\[smtp.relay1.com\]:2525" | wc -l | grep 1'
assert_success
# test default port
run /bin/sh -c 'cat ./test/relay/config/postfix-relaymap.cf | grep -e "^@example2.org\s\+\[smtp.relay2.com\]:25" | wc -l | grep 1'
assert_success
# test modifying
run /bin/sh -c 'cat ./test/relay/config/postfix-relaymap.cf | grep -e "^@example3.org\s\+\[smtp.relay.com\]:587" | wc -l | grep 1'
assert_success
}
@test "[No Existing Container] checking setup.sh: setup.sh relay add-auth" {
mkdir -p ./test/relay/config && echo -n > ./test/relay/config/postfix-sasl-password.cf
./setup.sh -p ./test/relay/config relay add-auth example.org smtp_user smtp_pass
./setup.sh -p ./test/relay/config relay add-auth example2.org smtp_user2 smtp_pass2
./setup.sh -p ./test/relay/config relay add-auth example2.org smtp_user2 smtp_pass_new
# test adding
run /bin/sh -c 'cat ./test/relay/config/postfix-sasl-password.cf | grep -e "^@example.org\s\+smtp_user:smtp_pass" | wc -l | grep 1'
assert_success
# test updating
run /bin/sh -c 'cat ./test/relay/config/postfix-sasl-password.cf | grep -e "^@example2.org\s\+smtp_user2:smtp_pass_new" | wc -l | grep 1'
assert_success
}
@test "[No Existing Container] checking setup.sh: setup.sh relay exclude-domain" {
mkdir -p ./test/relay/config && echo -n > ./test/relay/config/postfix-relaymap.cf
./setup.sh -p ./test/relay/config relay exclude-domain example.org
run /bin/sh -c 'cat ./test/relay/config/postfix-relaymap.cf | grep -e "^@example.org\s*$" | wc -l | grep 1'
assert_success
}

View file

@ -0,0 +1,465 @@
load 'test_helper/common'
export IMAGE_NAME CONTAINER_NAME TEST_FILE
IMAGE_NAME="${NAME:?Image name must be set}"
CONTAINER_NAME='open-dkim'
TEST_FILE='checking OpenDKIM: '
# WHY IS THIS CONTAINER EVEN CREATED WHEN MOST TESTS DO NOT USE IT?
function setup_file
{
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . "${CONTAINER_NAME}")
docker run -d \
--name "${CONTAINER_NAME}" \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "${PWD}/test/test-files":/tmp/docker-mailserver-test:ro \
-e DEFAULT_RELAY_HOST=default.relay.host.invalid:25 \
-e PERMIT_DOCKER=host \
-e LOG_LEVEL='trace' \
-h mail.my-domain.com \
-t "${IMAGE_NAME}"
wait_for_finished_setup_in_container "${CONTAINER_NAME}"
}
function teardown_file
{
docker rm -f "${CONTAINER_NAME}"
}
# -----------------------------------------------
# --- Actual Tests ------------------------------
# -----------------------------------------------
@test "${TEST_FILE}/etc/opendkim/KeyTable should contain 2 entries" {
run docker exec "${CONTAINER_NAME}" /bin/bash -c "cat /etc/opendkim/KeyTable | wc -l"
assert_success
assert_output 2
}
# TODO piping ls into grep ...
@test "${TEST_FILE}/etc/opendkim/keys/ should contain 2 entries" {
run docker exec "${CONTAINER_NAME}" /bin/bash -c "ls -l /etc/opendkim/keys/ | grep '^d' | wc -l"
assert_success
assert_output 2
}
@test "${TEST_FILE}/etc/opendkim.conf contains nameservers copied from /etc/resolv.conf" {
run docker exec "${CONTAINER_NAME}" /bin/bash -c \
"grep -E '^Nameservers ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' \
/etc/opendkim.conf"
assert_success
}
# this set of tests is of low quality. WHAT? <- DELETE AFTER REWRITE
# It does not test the RSA-Key size properly via openssl or similar WHAT??? <- DELETE AFTER REWRITE
# Instead it tests the file-size (here 861) - which may differ with a different domain names WWHHHHHHAAAT??? <- DELETE AFTER REWRITE
# TODO Needs complete re-write
@test "${TEST_FILE}generator creates default keys size" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_default_key_size)
# Prepare default key size 4096
rm -rf "${PRIVATE_CONFIG}/keyDefault"
mkdir -p "${PRIVATE_CONFIG}/keyDefault"
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/keyDefault/":/tmp/docker-mailserver/ \
-v "${PRIVATE_CONFIG}/postfix-accounts.cf":/tmp/docker-mailserver/postfix-accounts.cf \
-v "${PRIVATE_CONFIG}/postfix-virtual.cf":/tmp/docker-mailserver/postfix-virtual.cf \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim | wc -l'
assert_success
assert_output 6
run docker run --rm \
-v "${PRIVATE_CONFIG}/keyDefault/opendkim":/etc/opendkim \
"${IMAGE_NAME}" \
/bin/bash -c 'stat -c%s /etc/opendkim/keys/localhost.localdomain/mail.txt'
assert_success
assert_output 861
}
# this set of tests is of low quality. It does not test the RSA-Key size properly via openssl or similar <- DELETE AFTER REWRITE
# Instead it tests the file-size (here 861) - which may differ with a different domain names <- DELETE AFTER REWRITE
# TODO Needs complete re-write
@test "${TEST_FILE}generator creates key size 4096" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_key_size_4096)
rm -rf "${PRIVATE_CONFIG}/key4096"
mkdir -p "${PRIVATE_CONFIG}/config/key4096"
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/key2048/":/tmp/docker-mailserver/ \
-v "${PRIVATE_CONFIG}/postfix-accounts.cf":/tmp/docker-mailserver/postfix-accounts.cf \
-v "${PRIVATE_CONFIG}/postfix-virtual.cf":/tmp/docker-mailserver/postfix-virtual.cf \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim keysize 4096 | wc -l'
assert_success
assert_output 6
run docker run --rm \
-v "${PRIVATE_CONFIG}/key2048/opendkim":/etc/opendkim \
"${IMAGE_NAME}" \
/bin/bash -c 'stat -c%s /etc/opendkim/keys/localhost.localdomain/mail.txt'
assert_success
assert_output 861
}
# Instead it tests the file-size (here 511) - which may differ with a different domain names <- DELETE AFTER REWRITE
# This test may be re-used as a global test to provide better test coverage. <- DELETE AFTER REWRITE
# TODO Needs complete re-write
@test "${TEST_FILE}generator creates key size 2048" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_key_size_2048)
rm -rf "${PRIVATE_CONFIG}/key2048"
mkdir -p "${PRIVATE_CONFIG}/config/key2048"
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/key2048/":/tmp/docker-mailserver/ \
-v "${PRIVATE_CONFIG}/postfix-accounts.cf":/tmp/docker-mailserver/postfix-accounts.cf \
-v "${PRIVATE_CONFIG}/postfix-virtual.cf":/tmp/docker-mailserver/postfix-virtual.cf \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim keysize 2048 | wc -l'
assert_success
assert_output 6
run docker run --rm \
-v "${PRIVATE_CONFIG}/key2048/opendkim":/etc/opendkim \
"${IMAGE_NAME}" \
/bin/bash -c 'stat -c%s /etc/opendkim/keys/localhost.localdomain/mail.txt'
assert_success
assert_output 511
}
# this set of tests is of low quality. It does not test the RSA-Key size properly via openssl or similar <- DELETE AFTER REWRITE
# Instead it tests the file-size (here 329) - which may differ with a different domain names <- DELETE AFTER REWRITE
# TODO Needs complete re-write
@test "${TEST_FILE}generator creates key size 1024" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_key_size_1024)
rm -rf "${PRIVATE_CONFIG}/key1024"
mkdir -p "${PRIVATE_CONFIG}/key1024"
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/key1024/":/tmp/docker-mailserver/ \
-v "${PRIVATE_CONFIG}/postfix-accounts.cf":/tmp/docker-mailserver/postfix-accounts.cf \
-v "${PRIVATE_CONFIG}/postfix-virtual.cf":/tmp/docker-mailserver/postfix-virtual.cf \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim keysize 1024 | wc -l'
assert_success
assert_output 6
run docker run --rm \
-v "${PRIVATE_CONFIG}/key1024/opendkim":/etc/opendkim \
"${IMAGE_NAME}" \
/bin/bash -c 'stat -c%s /etc/opendkim/keys/localhost.localdomain/mail.txt'
assert_success
assert_output 329
}
@test "${TEST_FILE}generator creates keys, tables and TrustedHosts" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_dkim_generator_creates_keys_tables_TrustedHosts)
rm -rf "${PRIVATE_CONFIG}/empty"
mkdir -p "${PRIVATE_CONFIG}/empty"
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/empty/":/tmp/docker-mailserver/ \
-v "${PRIVATE_CONFIG}/postfix-accounts.cf":/tmp/docker-mailserver/postfix-accounts.cf \
-v "${PRIVATE_CONFIG}/postfix-virtual.cf":/tmp/docker-mailserver/postfix-virtual.cf \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim | wc -l'
assert_success
assert_output 6
# check keys for localhost.localdomain
run docker run --rm \
-v "${PRIVATE_CONFIG}/empty/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/localhost.localdomain/ | wc -l'
assert_success
assert_output 2
# check keys for otherdomain.tld
run docker run --rm \
-v "${PRIVATE_CONFIG}/empty/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/otherdomain.tld | wc -l'
assert_success
assert_output 2
# check presence of tables and TrustedHosts
run docker run --rm \
-v "${PRIVATE_CONFIG}/empty/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c "ls -1 /etc/opendkim | grep -E 'KeyTable|SigningTable|TrustedHosts|keys'|wc -l"
assert_success
assert_output 4
}
@test "${TEST_FILE}generator creates keys, tables and TrustedHosts without postfix-accounts.cf" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . )
rm -rf "${PRIVATE_CONFIG}/without-accounts"
mkdir -p "${PRIVATE_CONFIG}/without-accounts"
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/without-accounts/":/tmp/docker-mailserver/ \
-v "${PRIVATE_CONFIG}/postfix-virtual.cf":/tmp/docker-mailserver/postfix-virtual.cf \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim | wc -l'
assert_success
assert_output 5
# check keys for localhost.localdomain
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/without-accounts/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/localhost.localdomain/ | wc -l'
assert_success
assert_output 2
# check keys for otherdomain.tld
# run docker run --rm \
# -v "${PRIVATE_CONFIG}/without-accounts/opendkim":/etc/opendkim \
# "${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/otherdomain.tld | wc -l'
# assert_success
# [ "${output}" -eq 0 ]
# check presence of tables and TrustedHosts
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/without-accounts/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c "ls -1 /etc/opendkim | grep -E 'KeyTable|SigningTable|TrustedHosts|keys'|wc -l"
assert_success
assert_output 4
}
@test "${TEST_FILE}generator creates keys, tables and TrustedHosts without postfix-virtual.cf" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . "${BATS_TEST_NAME}")
rm -rf "${PRIVATE_CONFIG}/without-virtual"
mkdir -p "${PRIVATE_CONFIG}/without-virtual"
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/without-virtual/":/tmp/docker-mailserver/ \
-v "${PRIVATE_CONFIG}/postfix-accounts.cf":/tmp/docker-mailserver/postfix-accounts.cf \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim | wc -l'
assert_success
assert_output 5
# check keys for localhost.localdomain
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/without-virtual/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/localhost.localdomain/ | wc -l'
assert_success
assert_output 2
# check keys for otherdomain.tld
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/without-virtual/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/otherdomain.tld | wc -l'
assert_success
assert_output 2
# check presence of tables and TrustedHosts
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/without-virtual/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c "ls -1 /etc/opendkim | grep -E 'KeyTable|SigningTable|TrustedHosts|keys'|wc -l"
assert_success
assert_output 4
}
@test "${TEST_FILE}generator creates keys, tables and TrustedHosts using manual provided domain name" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . "${BATS_TEST_NAME}")
rm -rf "${PRIVATE_CONFIG}/with-domain" && mkdir -p "${PRIVATE_CONFIG}/with-domain"
# generate first key
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/":/tmp/docker-mailserver/ \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim keysize 2048 domain domain1.tld | wc -l'
assert_success
assert_output 4
# generate two additional keys different to the previous one
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/":/tmp/docker-mailserver/ \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim keysize 2048 domain "domain2.tld,domain3.tld" | wc -l'
assert_success
assert_output 2
# generate an additional key whilst providing already existing domains
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/":/tmp/docker-mailserver/ \
"${IMAGE_NAME}" /bin/bash -c 'open-dkim keysize 2048 domain "domain3.tld,domain4.tld" | wc -l'
assert_success
assert_output 1
# check keys for domain1.tld
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/domain1.tld/ | wc -l'
assert_success
assert_output 2
# check keys for domain2.tld
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/domain2.tld | wc -l'
assert_success
assert_output 2
# check keys for domain3.tld
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/domain3.tld | wc -l'
assert_success
assert_output 2
# check keys for domain4.tld
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c 'ls -1 /etc/opendkim/keys/domain4.tld | wc -l'
assert_success
assert_output 2
# check presence of tables and TrustedHosts
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c "ls -1 /etc/opendkim | grep -E 'KeyTable|SigningTable|TrustedHosts|keys' | wc -l"
assert_success
assert_output 4
# check valid entries actually present in KeyTable
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c \
"egrep 'domain1.tld|domain2.tld|domain3.tld|domain4.tld' /etc/opendkim/KeyTable | wc -l"
assert_success
assert_output 4
# check valid entries actually present in SigningTable
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-domain/opendkim":/etc/opendkim \
"${IMAGE_NAME}" /bin/bash -c \
"egrep 'domain1.tld|domain2.tld|domain3.tld|domain4.tld' /etc/opendkim/SigningTable | wc -l"
assert_success
assert_output 4
}
@test "${TEST_FILE}generator creates keys, tables and TrustedHosts using manual provided selector name" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . "${BATS_TEST_NAME}")
rm -rf "${PRIVATE_CONFIG}/with-selector" && mkdir -p "${PRIVATE_CONFIG}/with-selector"
# Generate first key
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-selector/":/tmp/docker-mailserver/ \
"${IMAGE_NAME:?}" /bin/sh -c "open-dkim keysize 2048 domain 'domain1.tld' selector mailer| wc -l"
assert_success
assert_output 4
# Check keys for domain1.tld
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-selector/opendkim":/etc/opendkim \
"${IMAGE_NAME:?}" /bin/sh -c 'ls -1 /etc/opendkim/keys/domain1.tld/ | wc -l'
assert_success
assert_output 2
# Check key names with selector for domain1.tld
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-selector/opendkim":/etc/opendkim \
"${IMAGE_NAME:?}" /bin/sh -c "ls -1 /etc/opendkim/keys/domain1.tld | grep -E 'mailer.private|mailer.txt' | wc -l"
assert_success
assert_output 2
# Check presence of tables and TrustedHosts
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-selector/opendkim":/etc/opendkim \
"${IMAGE_NAME:?}" /bin/sh -c "ls -1 /etc/opendkim | grep -E 'KeyTable|SigningTable|TrustedHosts|keys' | wc -l"
assert_success
assert_output 4
# Check valid entries actually present in KeyTable
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-selector/opendkim":/etc/opendkim \
"${IMAGE_NAME:?}" /bin/sh -c \
"grep 'domain1.tld' /etc/opendkim/KeyTable | wc -l"
assert_success
assert_output 1
# Check valid entries actually present in SigningTable
run docker run --rm \
-e LOG_LEVEL='trace' \
-v "${PRIVATE_CONFIG}/with-selector/opendkim":/etc/opendkim \
"${IMAGE_NAME:?}" /bin/sh -c \
"grep 'domain1.tld' /etc/opendkim/SigningTable | wc -l"
assert_success
assert_output 1
}

View file

@ -0,0 +1,89 @@
load 'test_helper/common'
NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME=non-default-docker-mail-network
setup_file() {
docker network create --driver bridge "${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}"
docker network create --driver bridge "${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}2"
# use two networks (default ("bridge") and our custom network) to recreate problematic test case where PERMIT_DOCKER=host would not help
# currently we cannot use --network in `docker run` multiple times, it will just use the last one
# instead we need to use create, network connect and start (see https://success.docker.com/article/multiple-docker-networks)
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_smtponly_second_network)
docker create --name mail_smtponly_second_network \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e SMTP_ONLY=1 \
-e PERMIT_DOCKER=connected-networks \
-e OVERRIDE_HOSTNAME=mail.my-domain.com \
--network "${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}" \
-t "${NAME}"
docker network connect "${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}2" mail_smtponly_second_network
docker start mail_smtponly_second_network
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_smtponly_second_network_sender)
docker run -d --name mail_smtponly_second_network_sender \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e SMTP_ONLY=1 \
-e PERMIT_DOCKER=connected-networks \
-e OVERRIDE_HOSTNAME=mail.my-domain.com \
--network "${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}2" \
-t "${NAME}"
# wait until postfix is up
wait_for_smtp_port_in_container mail_smtponly_second_network
# create another container that enforces authentication even on local connections
docker run -d --name mail_smtponly_force_authentication \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e SMTP_ONLY=1 \
-e PERMIT_DOCKER=none \
-e OVERRIDE_HOSTNAME=mail.my-domain.com \
-t "${NAME}"
# wait until postfix is up
wait_for_smtp_port_in_container mail_smtponly_force_authentication
}
teardown_file() {
docker logs mail_smtponly_second_network
docker rm -f mail_smtponly_second_network mail_smtponly_second_network_sender mail_smtponly_force_authentication
docker network rm "${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}" "${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}2"
}
@test "checking PERMIT_DOCKER: connected-networks" {
IPNET1=$(docker network inspect --format '{{(index .IPAM.Config 0).Subnet}}' non-default-docker-mail-network)
IPNET2=$(docker network inspect --format '{{(index .IPAM.Config 0).Subnet}}' non-default-docker-mail-network2)
run docker exec mail_smtponly_second_network /bin/sh -c "postconf | grep '^mynetworks ='"
assert_output --partial "${IPNET1}"
assert_output --partial "${IPNET2}"
run docker exec mail_smtponly_second_network /bin/sh -c "postconf smtp_host_lookup=no"
assert_success
run docker exec mail_smtponly_second_network /bin/sh -c "/etc/init.d/postfix reload"
assert_success
# we should be able to send from the other container on the second network!
run docker exec mail_smtponly_second_network_sender /bin/sh -c "nc mail_smtponly_second_network 25 < /tmp/docker-mailserver-test/email-templates/smtp-only.txt"
assert_output --partial "250 2.0.0 Ok: queued as "
repeat_until_success_or_timeout 60 run docker exec mail_smtponly_second_network /bin/sh -c 'grep -cE "to=<user2\@external.tld>.*status\=sent" /var/log/mail/mail.log'
[[ ${status} -ge 0 ]]
}
@test "checking PERMIT_DOCKER: none" {
run docker exec mail_smtponly_force_authentication /bin/sh -c "postconf smtp_host_lookup=no"
assert_success
run docker exec mail_smtponly_force_authentication /bin/sh -c "/etc/init.d/postfix reload"
assert_success
# the mailserver should require authentication and a protocol error should occur when using TLS
run docker exec mail_smtponly_force_authentication /bin/sh -c "nc localhost 25 < /tmp/docker-mailserver-test/email-templates/smtp-only.txt"
assert_output --partial "550 5.5.1 Protocol error"
[[ ${status} -ge 0 ]]
}

View file

@ -0,0 +1,243 @@
#!/usr/bin/env bats
load 'test_helper/common'
# Globals ${BATS_TMPDIR} and ${NAME}
# `${NAME}` defaults to `mailserver-testing:ci`
function teardown() {
docker rm -f tls_test_cipherlists
}
function setup_file() {
export DOMAIN="example.test"
export NETWORK="test-network"
# Shared config for TLS testing (read-only)
export TLS_CONFIG_VOLUME
TLS_CONFIG_VOLUME="$(pwd)/test/test-files/ssl/${DOMAIN}/:/config/ssl/:ro"
# `${BATS_TMPDIR}` maps to `/tmp`
export TLS_RESULTS_DIR="${BATS_TMPDIR}/results"
# NOTE: If the network already exists, test will fail to start.
docker network create "${NETWORK}"
# Copies all of `./test/config/` to specific directory for testing
# `${PRIVATE_CONFIG}` becomes `$(pwd)/test/duplicate_configs/<bats test filename>`
export PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# Pull `testssl.sh` image in advance to avoid it interfering with the `run` captured output.
# Only interferes (potential test failure) with `assert_output` not `assert_success`?
docker pull drwetter/testssl.sh:3.1dev
}
function teardown_file() {
docker network rm "${NETWORK}"
}
@test "checking tls: cipher list - rsa intermediate" {
check_ports 'rsa' 'intermediate'
}
@test "checking tls: cipher list - rsa modern" {
check_ports 'rsa' 'modern'
}
@test "checking tls: cipher list - ecdsa intermediate" {
check_ports 'ecdsa' 'intermediate'
}
@test "checking tls: cipher list - ecdsa modern" {
check_ports 'ecdsa' 'modern'
}
# Only ECDSA with RSA fallback is tested.
# There isn't a situation where RSA with ECDSA fallback would make sense.
@test "checking tls: cipher list - ecdsa intermediate, with rsa fallback" {
check_ports 'ecdsa' 'intermediate' 'rsa'
}
@test "checking tls: cipher list - ecdsa modern, with rsa fallback" {
check_ports 'ecdsa' 'modern' 'rsa'
}
function check_ports() {
local KEY_TYPE=$1
local TLS_LEVEL=$2
local ALT_KEY_TYPE=$3 # Optional parameter
local KEY_TYPE_LABEL="${KEY_TYPE}"
# This is just to add a `_` delimiter between the two key types for readability
if [[ -n ${ALT_KEY_TYPE} ]]
then
KEY_TYPE_LABEL="${KEY_TYPE}_${ALT_KEY_TYPE}"
fi
local RESULTS_PATH="${KEY_TYPE_LABEL}/${TLS_LEVEL}"
collect_cipherlist_data
# SMTP: Opportunistic STARTTLS Explicit(25)
# Needs to test against cipher lists specific to Port 25 ('_p25' parameter)
check_cipherlists "${RESULTS_PATH}/port_25.json" '_p25'
# SMTP Submission: Mandatory STARTTLS Explicit(587) and Implicit(465) TLS
check_cipherlists "${RESULTS_PATH}/port_587.json"
check_cipherlists "${RESULTS_PATH}/port_465.json"
# IMAP: Mandatory STARTTLS Explicit(143) and Implicit(993) TLS
check_cipherlists "${RESULTS_PATH}/port_143.json"
check_cipherlists "${RESULTS_PATH}/port_993.json"
# POP3: Mandatory STARTTLS Explicit(110) and Implicit(995)
check_cipherlists "${RESULTS_PATH}/port_110.json"
check_cipherlists "${RESULTS_PATH}/port_995.json"
}
function collect_cipherlist_data() {
local ALT_CERT=()
local ALT_KEY=()
if [[ -n ${ALT_KEY_TYPE} ]]
then
ALT_CERT=(--env SSL_ALT_CERT_PATH="/config/ssl/cert.${ALT_KEY_TYPE}.pem")
ALT_KEY=(--env SSL_ALT_KEY_PATH="/config/ssl/key.${ALT_KEY_TYPE}.pem")
fi
run docker run -d --name tls_test_cipherlists \
--volume "${PRIVATE_CONFIG}/:/tmp/docker-mailserver/" \
--volume "${TLS_CONFIG_VOLUME}" \
--env ENABLE_POP3=1 \
--env SSL_TYPE="manual" \
--env SSL_CERT_PATH="/config/ssl/cert.${KEY_TYPE}.pem" \
--env SSL_KEY_PATH="/config/ssl/key.${KEY_TYPE}.pem" \
"${ALT_CERT[@]}" \
"${ALT_KEY[@]}" \
--env TLS_LEVEL="${TLS_LEVEL}" \
--network "${NETWORK}" \
--network-alias "${DOMAIN}" \
--hostname "mail.${DOMAIN}" \
--tty \
"${NAME}" # Image name
assert_success
wait_for_tcp_port_in_container 25 tls_test_cipherlists
# NOTE: An rDNS query for the container IP will resolve to `<container name>.<network name>.`
# Make directory with test user ownership. Avoids Docker creating with root ownership.
# TODO: Can switch to filename prefix for JSON output when this is resolved: https://github.com/drwetter/testssl.sh/issues/1845
mkdir -p "${TLS_RESULTS_DIR}/${RESULTS_PATH}"
# For non-CI test runs, instead of removing prior test files after this test suite completes,
# they're retained and overwritten by future test runs instead. Useful for inspection.
# `--preference` reduces the test scope to the cipher suites reported as supported by the server. Completes in ~35% of the time.
local TESTSSL_CMD=(--quiet --file "/config/ssl/testssl.txt" --mode parallel --overwrite --preference)
# NOTE: Batch testing ports via `--file` doesn't properly bubble up failure.
# If the failure for a test is misleading consider testing a single port with:
# local TESTSSL_CMD=(--quiet --jsonfile-pretty "${RESULTS_PATH}/port_${PORT}.json" --starttls smtp "${DOMAIN}:${PORT}")
# TODO: Can use `jq` to check for failure when this is resolved: https://github.com/drwetter/testssl.sh/issues/1844
# `--user "<uid>:<gid>"` is a workaround: Avoids `permission denied` write errors for json output, uses `id` to match user uid & gid.
run docker run --rm \
--user "$(id -u):$(id -g)" \
--network "${NETWORK}" \
--volume "${TLS_CONFIG_VOLUME}" \
--volume "${TLS_RESULTS_DIR}/${RESULTS_PATH}/:/output" \
--workdir "/output" \
drwetter/testssl.sh:3.1dev "${TESTSSL_CMD[@]}"
assert_success
}
# Use `jq` to extract a specific cipher list from the target`testssl.sh` results json output file
function compare_cipherlist() {
local TARGET_CIPHERLIST=$1
local RESULTS_FILE=$2
local EXPECTED_CIPHERLIST=$3
run jq '.scanResult[0].serverPreferences[] | select(.id=="'"${TARGET_CIPHERLIST}"'") | .finding' "${TLS_RESULTS_DIR}/${RESULTS_FILE}"
assert_success
assert_output "${EXPECTED_CIPHERLIST}"
}
# Compares the expected cipher lists against logged test results from `testssl.sh`
function check_cipherlists() {
local RESULTS_FILE=$1
local p25=$2 # optional suffix
# TLS_LEVEL `modern` doesn't have TLS v1.0 or v1.1 cipher suites. Sets TLS v1.2 as minimum.
if [[ ${TLS_LEVEL} == "intermediate" ]]
then
compare_cipherlist "cipherorder_TLSv1" "${RESULTS_FILE}" "$(get_cipherlist "TLSv1${p25}")"
compare_cipherlist "cipherorder_TLSv1_1" "${RESULTS_FILE}" "$(get_cipherlist "TLSv1_1${p25}")"
fi
compare_cipherlist "cipherorder_TLSv1_2" "${RESULTS_FILE}" "$(get_cipherlist "TLSv1_2${p25}")"
compare_cipherlist "cipherorder_TLSv1_3" "${RESULTS_FILE}" "$(get_cipherlist 'TLSv1_3')"
}
# Expected cipher lists. Should match `TLS_LEVEL` cipher lists set in `start-mailserver.sh`.
# Excluding Port 25 which uses defaults from Postfix after applying `smtpd_tls_exclude_ciphers` rules.
# NOTE: If a test fails, look at the `check_ports` params, then update the corresponding associative key's value
# with the `actual` error value (assuming an update needs to be made, and not a valid security issue to look into).
function get_cipherlist() {
local TLS_VERSION=$1
if [[ ${TLS_VERSION} == "TLSv1_3" ]]
then
# TLS v1.3 cipher suites are not user defineable and not unique to the available certificate(s).
# They do not support server enforced order either.
echo '"TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256"'
else
# Associative array for easy querying of required cipher list
declare -A CIPHER_LIST
# `intermediate` cipher lists TLS v1.0 and v1.1 cipher suites should be the same:
CIPHER_LIST["rsa_intermediate_TLSv1"]='"ECDHE-RSA-AES128-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA"'
CIPHER_LIST["rsa_intermediate_TLSv1_1"]=${CIPHER_LIST["rsa_intermediate_TLSv1"]}
CIPHER_LIST["rsa_intermediate_TLSv1_2"]='"ECDHE-RSA-CHACHA20-POLY1305 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES128-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA"'
# `modern` cipher lists shouldn't have TLS v1.0 or v1.1 cipher suites:
CIPHER_LIST["rsa_modern_TLSv1_2"]='"ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384"'
# ECDSA:
CIPHER_LIST["ecdsa_intermediate_TLSv1"]='"ECDHE-ECDSA-AES128-SHA ECDHE-ECDSA-AES256-SHA"'
CIPHER_LIST["ecdsa_intermediate_TLSv1_1"]=${CIPHER_LIST["ecdsa_intermediate_TLSv1"]}
CIPHER_LIST["ecdsa_intermediate_TLSv1_2"]='"ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES128-SHA ECDHE-ECDSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA"'
CIPHER_LIST["ecdsa_modern_TLSv1_2"]='"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305"'
# ECDSA + RSA fallback, dual cert support:
CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1"]='"ECDHE-ECDSA-AES128-SHA ECDHE-RSA-AES128-SHA ECDHE-ECDSA-AES256-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA"'
CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_1"]=${CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1"]}
CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_2"]='"ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 ECDHE-ECDSA-AES128-SHA ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES128-SHA ECDHE-ECDSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA"'
CIPHER_LIST["ecdsa_rsa_modern_TLSv1_2"]='"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384"'
# Port 25
# TLSv1 and TLSv1_1 share the same cipher suites as other ports have. But the server order differs:
CIPHER_LIST["rsa_intermediate_TLSv1_p25"]='"ECDHE-RSA-AES256-SHA DHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA DHE-RSA-AES128-SHA"'
CIPHER_LIST["rsa_intermediate_TLSv1_1_p25"]=${CIPHER_LIST["rsa_intermediate_TLSv1_p25"]}
# TLSv1_2 has different server order and also includes ARIA, CCM, DHE+CHACHA20-POLY1305 cipher suites:
CIPHER_LIST["rsa_intermediate_TLSv1_2_p25"]='"ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES256-CCM8 DHE-RSA-AES256-CCM ECDHE-ARIA256-GCM-SHA384 DHE-RSA-ARIA256-GCM-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ECDHE-RSA-AES256-SHA DHE-RSA-AES256-SHA ARIA256-GCM-SHA384 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-CCM8 DHE-RSA-AES128-CCM ECDHE-ARIA128-GCM-SHA256 DHE-RSA-ARIA128-GCM-SHA256 ECDHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA256 ECDHE-RSA-AES128-SHA DHE-RSA-AES128-SHA ARIA128-GCM-SHA256"'
# Port 25 is unaffected by `TLS_LEVEL` profiles (other than min TLS version), it has the same TLS v1.2 cipher list under both:
CIPHER_LIST["rsa_modern_TLSv1_2_p25"]=${CIPHER_LIST["rsa_intermediate_TLSv1_2_p25"]}
# ECDSA (Port 25):
CIPHER_LIST["ecdsa_intermediate_TLSv1_p25"]='"ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA"'
CIPHER_LIST["ecdsa_intermediate_TLSv1_1_p25"]=${CIPHER_LIST["ecdsa_intermediate_TLSv1_p25"]}
CIPHER_LIST["ecdsa_intermediate_TLSv1_2_p25"]='"ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-CCM8 ECDHE-ECDSA-AES256-CCM ECDHE-ECDSA-ARIA256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-CCM8 ECDHE-ECDSA-AES128-CCM ECDHE-ECDSA-ARIA128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES128-SHA"'
CIPHER_LIST["ecdsa_modern_TLSv1_2_p25"]=${CIPHER_LIST["ecdsa_intermediate_TLSv1_2_p25"]}
# ECDSA + RSA fallback, dual cert support (Port 25):
CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_p25"]='"ECDHE-ECDSA-AES256-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES256-SHA ECDHE-ECDSA-AES128-SHA ECDHE-RSA-AES128-SHA DHE-RSA-AES128-SHA"'
CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_1_p25"]=${CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_p25"]}
CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_2_p25"]='"ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-CCM8 ECDHE-ECDSA-AES256-CCM DHE-RSA-AES256-CCM8 DHE-RSA-AES256-CCM ECDHE-ECDSA-ARIA256-GCM-SHA384 ECDHE-ARIA256-GCM-SHA384 DHE-RSA-ARIA256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ECDHE-ECDSA-AES256-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES256-SHA ARIA256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-CCM8 ECDHE-ECDSA-AES128-CCM DHE-RSA-AES128-CCM8 DHE-RSA-AES128-CCM ECDHE-ECDSA-ARIA128-GCM-SHA256 ECDHE-ARIA128-GCM-SHA256 DHE-RSA-ARIA128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA256 ECDHE-ECDSA-AES128-SHA ECDHE-RSA-AES128-SHA DHE-RSA-AES128-SHA ARIA128-GCM-SHA256"'
CIPHER_LIST["ecdsa_rsa_modern_TLSv1_2_p25"]=${CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_2_p25"]}
local TARGET_QUERY="${KEY_TYPE_LABEL}_${TLS_LEVEL}_${TLS_VERSION}"
echo "${CIPHER_LIST[${TARGET_QUERY}]}"
fi
}

View file

@ -0,0 +1,88 @@
load 'test_helper/common'
CONTAINER='sedfile'
TEST_FILE='/tmp/sedfile-test.txt'
# prepare tests
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG="$(duplicate_config_for_container . )"
docker run -d --name "${CONTAINER}" \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-h mail.my-domain.com "${NAME}"
wait_for_finished_setup_in_container "${CONTAINER}"
}
function setup() {
# create test file
docker exec "${CONTAINER}" bash -c 'echo "foo bar" > "'"${TEST_FILE}"'"'
}
@test "checking sedfile parameter count" {
run docker exec "${CONTAINER}" sedfile
assert_failure
assert_output --partial 'At least three parameters must be given'
}
@test "checking sedfile substitute success" {
# change 'bar' to 'baz'
run docker exec "${CONTAINER}" sedfile -i 's|bar|baz|' "${TEST_FILE}"
assert_success
assert_output ''
# file modified?
run docker exec "${CONTAINER}" cat "${TEST_FILE}"
assert_success
assert_output 'foo baz'
}
@test "checking sedfile substitute failure (on first container start)" {
# delete marker
run docker exec "${CONTAINER}" rm '/CONTAINER_START'
assert_success
# try to change 'baz' to 'something' and fail
run docker exec "${CONTAINER}" sedfile -i 's|baz|something|' "${TEST_FILE}"
assert_failure
assert_output --partial "No difference after call to 'sed' in 'sedfile' (sed -i s|baz|something| /tmp/sedfile-test.txt)"
# file unchanged?
run docker exec "${CONTAINER}" cat "${TEST_FILE}"
assert_success
assert_output 'foo bar'
# recreate marker
run docker exec "${CONTAINER}" touch '/CONTAINER_START'
assert_success
}
@test "checking sedfile silent failure on substitute (when DMS was restarted)" {
# try to change 'baz' to 'something' and fail silently
run docker exec "${CONTAINER}" sedfile -i 's|baz|something|' "${TEST_FILE}"
assert_success
assert_output ''
# file unchanged?
run docker exec "${CONTAINER}" cat "${TEST_FILE}"
assert_success
assert_output 'foo bar'
}
@test "checking sedfile substitude failure (strict)" {
# try to change 'baz' to 'something' and fail
run docker exec "${CONTAINER}" sedfile --strict -i 's|baz|something|' "${TEST_FILE}"
assert_failure
assert_output --partial "No difference after call to 'sed' in 'sedfile' (sed -i s|baz|something| /tmp/sedfile-test.txt)"
# file unchanged?
run docker exec "${CONTAINER}" cat "${TEST_FILE}"
assert_success
assert_output 'foo bar'
}
# clean up
function teardown_file() {
docker rm -f "${CONTAINER}"
}

View file

@ -0,0 +1,289 @@
load 'test_helper/common'
# Globals referenced from `test_helper/common`:
# TEST_NAME (should match the filename, minus the bats extension)
# This is a bare minimal container setup.
# All test-cases run sequentially against the same container instance,
# no state is reset between test-cases.
function setup_file() {
# Initializes common default vars to prepare a DMS container with:
init_with_defaults
# Creates and starts the container with additional ENV needed:
# `LOG_LEVEL=debug` required for using `wait_until_change_detection_event_completes()`
# shellcheck disable=SC2034
local CONTAINER_ARGS_ENV_CUSTOM=(
--env LOG_LEVEL='debug'
)
common_container_setup 'CONTAINER_ARGS_ENV_CUSTOM'
}
function teardown_file() {
docker rm -f "${TEST_NAME}"
}
@test "checking setup.sh: show usage when no arguments provided" {
run ./setup.sh
assert_success
assert_output --partial "This is the main administration script that you use for all your interactions with"
}
@test "checking setup.sh: exit with error when wrong arguments provided" {
run ./setup.sh lol troll
assert_failure
assert_line --index 0 --partial "The command 'lol troll' is invalid."
}
# Create a new account for subsequent tests to depend upon
@test "checking setup.sh: setup.sh email add and login" {
local MAIL_ACCOUNT='user@example.com'
local MAIL_PASS='test_password'
local DATABASE_ACCOUNTS="${TEST_TMP_CONFIG}/postfix-accounts.cf"
# Create an account
run ./setup.sh -c "${TEST_NAME}" email add "${MAIL_ACCOUNT}" "${MAIL_PASS}"
assert_success
# Verify account was added to `postfix-accounts.cf`:
local ACCOUNT
ACCOUNT=$(grep "${MAIL_ACCOUNT}" "${DATABASE_ACCOUNTS}" | awk -F '|' '{print $1}')
assert_equal "${ACCOUNT}" "${MAIL_ACCOUNT}"
# Wait for change detection event to complete (create maildir and add account to Dovecot UserDB+PassDB)
wait_until_account_maildir_exists "${TEST_NAME}" "${MAIL_ACCOUNT}"
# Dovecot is stopped briefly at the end of processing a change event (should change to reload in future),
# to more accurately use `wait_for_service` ensure you wait until `changedetector` is done.
wait_until_change_detection_event_completes "${TEST_NAME}"
wait_for_service "${TEST_NAME}" dovecot
# Verify account authentication is successful:
local RESPONSE
RESPONSE=$(docker exec "${TEST_NAME}" doveadm auth test "${MAIL_ACCOUNT}" "${MAIL_PASS}" | grep 'passdb')
assert_equal "${RESPONSE}" "passdb: ${MAIL_ACCOUNT} auth succeeded"
}
@test "checking setup.sh: setup.sh email list" {
run ./setup.sh -c "${TEST_NAME}" email list
assert_success
}
# Update an existing account
@test "checking setup.sh: setup.sh email update" {
local MAIL_ACCOUNT='user@example.com'
local MAIL_PASS='test_password'
local DATABASE_ACCOUNTS="${TEST_TMP_CONFIG}/postfix-accounts.cf"
# `postfix-accounts.cf` should already have an account with a non-empty hashed password:
local MAIL_PASS_HASH
MAIL_PASS_HASH=$(grep "${MAIL_ACCOUNT}" "${DATABASE_ACCOUNTS}" | awk -F '|' '{print $2}')
assert_not_equal "${MAIL_PASS_HASH}" ""
# Update the password should be successful:
local NEW_PASS='new_password'
run ./setup.sh -c "${TEST_NAME}" email update "${MAIL_ACCOUNT}" "${NEW_PASS}"
refute_output --partial 'Password must not be empty'
assert_success
# NOTE: this was put in place for the next test `setup.sh email del` to properly work.
wait_until_change_detection_event_completes "${TEST_NAME}"
# `postfix-accounts.cf` should have an updated password hash stored:
local NEW_PASS_HASH
NEW_PASS_HASH=$(grep "${MAIL_ACCOUNT}" "${DATABASE_ACCOUNTS}" | awk -F '|' '{print $2}')
assert_not_equal "${NEW_PASS_HASH}" ""
assert_not_equal "${NEW_PASS_HASH}" "${MAIL_PASS_HASH}"
# Verify Dovecot derives NEW_PASS_HASH from NEW_PASS:
run docker exec "${TEST_NAME}" doveadm pw -t "${NEW_PASS_HASH}" -p "${NEW_PASS}"
refute_output 'Fatal: reverse password verification check failed: Password mismatch'
assert_output "${NEW_PASS_HASH} (verified)"
}
# Delete an existing account
# WARNING: While this feature works via the internal `setup` command, the external `setup.sh`
# has no support to mount a volume to `/var/mail` (only via `-c` to use a running container),
# thus the `-y` option to delete the account maildir has no effect nor informs the user.
# https://github.com/docker-mailserver/docker-mailserver/issues/949
@test "checking setup.sh: setup.sh email del" {
local MAIL_ACCOUNT='user@example.com'
local MAIL_PASS='test_password'
# Account deletion is successful:
run ./setup.sh -c "${TEST_NAME}" email del -y "${MAIL_ACCOUNT}"
assert_success
# NOTE: Sometimes the directory still exists, possibly from change detection
# of the previous test (`email udpate`) triggering. Therefore, the function
# `wait_until_change_detection_event_completes was added to the
# `setup.sh email update` test.
repeat_in_container_until_success_or_timeout 60 "${TEST_NAME}" bash -c '[[ ! -d /var/mail/example.com/user ]]'
# Account is not present in `postfix-accounts.cf`:
run grep "${MAIL_ACCOUNT}" "${TEST_TMP_CONFIG}/postfix-accounts.cf"
assert_failure
# NOTE: Actual account will still exist briefly in Dovecot UserDB+PassDB
# until `changedetector` service is triggered by `postfix-accounts.cf`
# which will rebuild Dovecots accounts from scratch.
}
@test "checking setup.sh: setup.sh email restrict" {
run ./setup.sh -c "${TEST_NAME}" email restrict
assert_failure
run ./setup.sh -c "${TEST_NAME}" email restrict add
assert_failure
./setup.sh -c "${TEST_NAME}" email restrict add send lorem@impsum.org
run ./setup.sh -c "${TEST_NAME}" email restrict list send
assert_output --regexp "^lorem@impsum.org.*REJECT"
run ./setup.sh -c "${TEST_NAME}" email restrict del send lorem@impsum.org
assert_success
run ./setup.sh -c "${TEST_NAME}" email restrict list send
assert_output --partial "Everyone is allowed"
./setup.sh -c "${TEST_NAME}" email restrict add receive rec_lorem@impsum.org
run ./setup.sh -c "${TEST_NAME}" email restrict list receive
assert_output --regexp "^rec_lorem@impsum.org.*REJECT"
run ./setup.sh -c "${TEST_NAME}" email restrict del receive rec_lorem@impsum.org
assert_success
}
# alias
@test "checking setup.sh: setup.sh alias list" {
run ./setup.sh -c "${TEST_NAME}" alias list
assert_success
assert_output --partial "alias1@localhost.localdomain user1@localhost.localdomain"
assert_output --partial "@localdomain2.com user1@localhost.localdomain"
}
@test "checking setup.sh: setup.sh alias add" {
./setup.sh -c "${TEST_NAME}" alias add alias@example.com target1@forward.com
./setup.sh -c "${TEST_NAME}" alias add alias@example.com target2@forward.com
./setup.sh -c "${TEST_NAME}" alias add alias2@example.org target3@forward.com
sleep 5
run grep "alias@example.com target1@forward.com,target2@forward.com" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_success
}
@test "checking setup.sh: setup.sh alias del" {
./setup.sh -c "${TEST_NAME}" alias del alias@example.com target1@forward.com
run grep "target1@forward.com" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_failure
run grep "target2@forward.com" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_output "alias@example.com target2@forward.com"
./setup.sh -c "${TEST_NAME}" alias del alias@example.org target2@forward.com
run grep "alias@example.org" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_failure
run grep "alias2@example.org" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_success
./setup.sh -c "${TEST_NAME}" alias del alias2@example.org target3@forward.com
run grep "alias2@example.org" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_failure
}
# quota
@test "checking setup.sh: setup.sh setquota" {
./setup.sh -c "${TEST_NAME}" email add quota_user@example.com test_password
./setup.sh -c "${TEST_NAME}" email add quota_user2@example.com test_password
run ./setup.sh -c "${TEST_NAME}" quota set quota_user@example.com 12M
assert_success
run ./setup.sh -c "${TEST_NAME}" quota set 51M quota_user@example.com
assert_failure
run ./setup.sh -c "${TEST_NAME}" quota set unknown@domain.com 150M
assert_failure
run ./setup.sh -c "${TEST_NAME}" quota set quota_user2 51M
assert_failure
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:12M\$' | wc -l | grep 1"
assert_success
run ./setup.sh -c "${TEST_NAME}" quota set quota_user@example.com 26M
assert_success
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:26M\$' | wc -l | grep 1"
assert_success
run grep "quota_user2@example.com" "${TEST_TMP_CONFIG}/dovecot-quotas.cf"
assert_failure
}
# `quota_user@example.com` created in previous `setquota` test
@test "checking setup.sh: setup.sh delquota" {
run ./setup.sh -c "${TEST_NAME}" quota set quota_user@example.com 12M
assert_success
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:12M\$' | wc -l | grep 1"
assert_success
run ./setup.sh -c "${TEST_NAME}" quota del unknown@domain.com
assert_failure
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:12M\$' | wc -l | grep 1"
assert_success
run ./setup.sh -c "${TEST_NAME}" quota del quota_user@example.com
assert_success
run grep "quota_user@example.com" "${TEST_TMP_CONFIG}/dovecot-quotas.cf"
assert_failure
}
@test "checking setup.sh: setup.sh config dkim help correctly displayed" {
run ./setup.sh -c "${TEST_NAME}" config dkim help
assert_success
assert_line --index 3 --partial " open-dkim - configure DomainKeys Identified Mail (DKIM)"
}
# debug
@test "checking setup.sh: setup.sh debug fetchmail" {
run ./setup.sh -c "${TEST_NAME}" debug fetchmail
assert_failure
assert_output --partial "fetchmail: normal termination, status 11"
}
@test "checking setup.sh: setup.sh debug login ls" {
run ./setup.sh -c "${TEST_NAME}" debug login ls
assert_success
}
@test "checking setup.sh: setup.sh relay add-domain" {
./setup.sh -c "${TEST_NAME}" relay add-domain example1.org smtp.relay1.com 2525
./setup.sh -c "${TEST_NAME}" relay add-domain example2.org smtp.relay2.com
./setup.sh -c "${TEST_NAME}" relay add-domain example3.org smtp.relay3.com 2525
./setup.sh -c "${TEST_NAME}" relay add-domain example3.org smtp.relay.com 587
# check adding
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-relaymap.cf | grep -e '^@example1.org\s\+\[smtp.relay1.com\]:2525' | wc -l | grep 1"
assert_success
# test default port
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-relaymap.cf | grep -e '^@example2.org\s\+\[smtp.relay2.com\]:25' | wc -l | grep 1"
assert_success
# test modifying
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-relaymap.cf | grep -e '^@example3.org\s\+\[smtp.relay.com\]:587' | wc -l | grep 1"
assert_success
}
@test "checking setup.sh: setup.sh relay add-auth" {
./setup.sh -c "${TEST_NAME}" relay add-auth example.org smtp_user smtp_pass
./setup.sh -c "${TEST_NAME}" relay add-auth example2.org smtp_user2 smtp_pass2
./setup.sh -c "${TEST_NAME}" relay add-auth example2.org smtp_user2 smtp_pass_new
# test adding
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-sasl-password.cf | grep -e '^@example.org\s\+smtp_user:smtp_pass' | wc -l | grep 1"
assert_success
# test updating
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-sasl-password.cf | grep -e '^@example2.org\s\+smtp_user2:smtp_pass_new' | wc -l | grep 1"
assert_success
}
@test "checking setup.sh: setup.sh relay exclude-domain" {
./setup.sh -c "${TEST_NAME}" relay exclude-domain example.org
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-relaymap.cf | grep -e '^@example.org\s*$' | wc -l | grep 1"
assert_success
}

View file

@ -0,0 +1,271 @@
load 'test_helper/bats-support/load'
load 'test_helper/bats-assert/load'
load 'test_helper/common'
@test "repeat_until_success_or_timeout returns instantly on success" {
SECONDS=0
repeat_until_success_or_timeout 1 true
[[ ${SECONDS} -le 1 ]]
}
@test "repeat_until_success_or_timeout waits for timeout on persistent failure" {
SECONDS=0
run repeat_until_success_or_timeout 2 false
[[ ${SECONDS} -ge 2 ]]
assert_failure
assert_output --partial "Timed out on command"
}
@test "repeat_until_success_or_timeout aborts immediately on fatal failure" {
SECONDS=0
run repeat_until_success_or_timeout --fatal-test false 2 false
[[ ${SECONDS} -le 1 ]]
assert_failure
assert_output --partial "early aborting"
}
@test "repeat_until_success_or_timeout expects integer timeout" {
run repeat_until_success_or_timeout 1 true
assert_success
run repeat_until_success_or_timeout timeout true
assert_failure
run repeat_until_success_or_timeout --fatal-test true timeout true
assert_failure
}
@test "run_until_success_or_timeout returns instantly on success" {
SECONDS=0
run_until_success_or_timeout 2 true
[[ ${SECONDS} -le 1 ]]
assert_success
}
@test "run_until_success_or_timeout waits for timeout on persistent failure" {
SECONDS=0
! run_until_success_or_timeout 2 false
[[ ${SECONDS} -ge 2 ]]
assert_failure
}
@test "repeat_in_container_until_success_or_timeout fails immediately for non-running container" {
SECONDS=0
! repeat_in_container_until_success_or_timeout 10 name-of-non-existing-container true
[[ ${SECONDS} -le 1 ]]
}
@test "repeat_in_container_until_success_or_timeout run command in container" {
local CONTAINER_NAME
CONTAINER_NAME=$(docker run --rm -d alpine sleep 100)
SECONDS=0
! repeat_in_container_until_success_or_timeout 10 "${CONTAINER_NAME}" sh -c "echo '${CONTAINER_NAME}' > /tmp/marker"
[[ ${SECONDS} -le 1 ]]
run docker exec "${CONTAINER_NAME}" cat /tmp/marker
assert_output "${CONTAINER_NAME}"
}
@test "container_is_running" {
local CONTAINER_NAME
CONTAINER_NAME=$(docker run --rm -d alpine sleep 100)
container_is_running "${CONTAINER_NAME}"
docker rm -f "${CONTAINER_NAME}"
! container_is_running "${CONTAINER_NAME}"
}
@test "wait_for_smtp_port_in_container aborts wait after timeout" {
local CONTAINER_NAME
CONTAINER_NAME=$(docker run --rm -d alpine sleep 100)
SECONDS=0
TEST_TIMEOUT_IN_SECONDS=2 run wait_for_smtp_port_in_container "${CONTAINER_NAME}"
[[ ${SECONDS} -ge 2 ]]
assert_failure
assert_output --partial "Timed out on command"
}
# NOTE: Test requires external network access available
@test "wait_for_smtp_port_in_container returns immediately when port found" {
local CONTAINER_NAME
CONTAINER_NAME=$(docker run --rm -d alpine sh -c "sleep 10")
docker exec "${CONTAINER_NAME}" apk add netcat-openbsd
docker exec "${CONTAINER_NAME}" nc -l 25 &
SECONDS=0
TEST_TIMEOUT_IN_SECONDS=5 run wait_for_smtp_port_in_container "${CONTAINER_NAME}"
[[ ${SECONDS} -lt 5 ]]
assert_success
}
@test "wait_for_finished_setup_in_container" {
# variable not local to make visible to teardown
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
CONTAINER_NAME=$(docker run -d --rm \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-h mail.my-domain.com \
-t "${NAME}")
teardown() { docker rm -f "${CONTAINER_NAME}"; }
# the setup should not be finished immediately after starting
! TEST_TIMEOUT_IN_SECONDS=0 wait_for_finished_setup_in_container "${CONTAINER_NAME}"
# but it will finish eventually
SECONDS=1
wait_for_finished_setup_in_container "${CONTAINER_NAME}"
[[ ${SECONDS} -gt 0 ]]
}
@test "duplicate_config_for_container" {
local path
path=$(duplicate_config_for_container duplicate_config_test)
run cat "${path}/marker"
assert_line "This marker file is there to identify the correct config being copied"
run duplicate_config_for_container non-existant-source-folder "${BATS_TEST_NAME}2"
assert_failure
}
@test "container_has_service_running/wait_for_service" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# variable not local to make visible to teardown
CONTAINER_NAME=$(docker run -d --rm \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-h mail.my-domain.com \
-t "${NAME}")
teardown() { docker rm -f "${CONTAINER_NAME}"; }
# pick a service that was not started
! container_has_service_running "${CONTAINER_NAME}" clamav
# wait for a service that should be started
wait_for_service "${CONTAINER_NAME}" postfix
# shut down the service
docker exec "${CONTAINER_NAME}" supervisorctl stop postfix
# now it should be off
SECONDS=0
TEST_TIMEOUT_IN_SECONDS=5 run wait_for_service "${CONTAINER_NAME}" postfix
[[ ${SECONDS} -ge 5 ]]
assert_failure
}
@test "wait_for_changes_to_be_detected_in_container fails when timeout is reached" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# variable not local to make visible to teardown
CONTAINER_NAME=$(docker run -d --rm \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-h mail.my-domain.com \
-t "${NAME}")
teardown() { docker rm -f "${CONTAINER_NAME}"; }
# wait for the initial checksum file to be created
# shellcheck disable=SC2016
repeat_in_container_until_success_or_timeout 60 "${CONTAINER_NAME}" bash -c 'source /usr/local/bin/helpers/index.sh; test -e "${CHKSUM_FILE}"'
# there should be no changes in the beginning
TEST_TIMEOUT_IN_SECONDS=0 wait_for_changes_to_be_detected_in_container "${CONTAINER_NAME}"
# trigger some change
docker exec "${CONTAINER_NAME}" /bin/sh -c "addmailuser auser3@mail.my-domain.com mypassword"
# that should be picked up as not yet detected
! TEST_TIMEOUT_IN_SECONDS=0 wait_for_changes_to_be_detected_in_container "${CONTAINER_NAME}"
}
@test "wait_for_changes_to_be_detected_in_container succeeds within timeout" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# variable not local to make visible to teardown
CONTAINER_NAME=$(docker run -d --rm \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-h mail.my-domain.com \
-t "${NAME}")
teardown() { docker rm -f "${CONTAINER_NAME}"; }
# wait for the initial checksum file to be created
# shellcheck disable=SC2016
repeat_in_container_until_success_or_timeout 60 "${CONTAINER_NAME}" bash -c 'source /usr/local/bin/helpers/index.sh; test -e "${CHKSUM_FILE}"'
# trigger some change
docker exec "${CONTAINER_NAME}" /bin/sh -c "addmailuser auser3@mail.my-domain.com mypassword"
# that should eventually be detected
SECONDS=0
wait_for_changes_to_be_detected_in_container "${CONTAINER_NAME}"
[[ ${SECONDS} -gt 0 ]]
}
# TODO investigate why this test fails
@test "wait_for_empty_mail_queue_in_container fails when timeout reached" {
skip 'disabled as it fails randomly: https://github.com/docker-mailserver/docker-mailserver/pull/2177'
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# variable not local to make visible to teardown
# enable ClamAV to make message delivery slower, so we can detect it
CONTAINER_NAME=$(docker run -d --rm \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_CLAMAV=1 \
-h mail.my-domain.com \
-t "${NAME}")
teardown() { docker rm -f "${CONTAINER_NAME}"; }
wait_for_smtp_port_in_container "${CONTAINER_NAME}" || docker logs "${CONTAINER_NAME}"
SECONDS=0
# no mails -> should return immediately
TEST_TIMEOUT_IN_SECONDS=5 wait_for_empty_mail_queue_in_container "${CONTAINER_NAME}"
[[ ${SECONDS} -lt 5 ]]
# fill the queue with a message
docker exec "${CONTAINER_NAME}" /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-virus.txt"
# that should still be stuck in the queue
! TEST_TIMEOUT_IN_SECONDS=0 wait_for_empty_mail_queue_in_container "${CONTAINER_NAME}"
}
# TODO investigate why this test fails
@test "wait_for_empty_mail_queue_in_container succeeds within timeout" {
skip 'disabled as it fails randomly: https://github.com/docker-mailserver/docker-mailserver/pull/2177'
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# variable not local to make visible to teardown
# enable ClamAV to make message delivery slower, so we can detect it
CONTAINER_NAME=$(docker run -d --rm \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_CLAMAV=1 \
-h mail.my-domain.com \
-t "${NAME}")
teardown() { docker rm -f "${CONTAINER_NAME}"; }
wait_for_smtp_port_in_container "${CONTAINER_NAME}" || docker logs "${CONTAINER_NAME}"
# fill the queue with a message
docker exec "${CONTAINER_NAME}" /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-virus.txt"
# give it some time to clear the queue
SECONDS=0
wait_for_empty_mail_queue_in_container "${CONTAINER_NAME}"
[[ ${SECONDS} -gt 0 ]]
}

View file

@ -0,0 +1,982 @@
load 'test_helper/common'
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail)
mv "${PRIVATE_CONFIG}/user-patches/user-patches.sh" "${PRIVATE_CONFIG}/user-patches.sh"
# `LOG_LEVEL=debug` required for using `wait_until_change_detection_event_completes()`
docker run --rm -d --name mail \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-v "$(pwd)/test/onedir":/var/mail-state \
-e AMAVIS_LOGLEVEL=2 \
-e CLAMAV_MESSAGE_SIZE_LIMIT=30M \
-e ENABLE_CLAMAV=0 \
-e ENABLE_MANAGESIEVE=1 \
-e ENABLE_QUOTAS=1 \
-e ENABLE_SPAMASSASSIN=1 \
-e ENABLE_SRS=1 \
-e ENABLE_UPDATE_CHECK=0 \
-e LOG_LEVEL='debug' \
-e PERMIT_DOCKER=host \
-e PFLOGSUMM_TRIGGER=logrotate \
-e REPORT_RECIPIENT=user1@localhost.localdomain \
-e REPORT_SENDER=report1@mail.my-domain.com \
-e SA_KILL=3.0 \
-e SA_SPAM_SUBJECT="SPAM: " \
-e SA_TAG=-5.0 \
-e SA_TAG2=2.0 \
-e SPAMASSASSIN_SPAM_TO_INBOX=0 \
-e SPOOF_PROTECTION=1 \
-e SSL_TYPE='snakeoil' \
-e VIRUSMAILS_DELETE_DELAY=7 \
--hostname mail.my-domain.com \
--tty \
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)" \
--health-cmd "ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1" \
"${NAME}"
wait_for_finished_setup_in_container mail
# generate accounts after container has been started
docker exec mail setup email add 'added@localhost.localdomain' 'mypassword'
docker exec mail setup email add 'pass@localhost.localdomain' 'may be \a `p^a.*ssword'
# setup sieve
docker cp "${PRIVATE_CONFIG}/sieve/dovecot.sieve" mail:/var/mail/localhost.localdomain/user1/.dovecot.sieve
# this relies on the checksum file being updated after all changes have been applied
wait_until_change_detection_event_completes mail
wait_for_service mail postfix
wait_for_smtp_port_in_container mail
# The first mail sent leverages an assert for better error output if a failure occurs:
run docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
assert_success
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-local.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-alias-recipient-delimiter.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user2.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user3.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-added.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user-and-cc-local-alias.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-regexp-alias-external.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-regexp-alias-local.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-catchall-local.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/sieve-spam-folder.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/sieve-pipe.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 /bin/sh -c "sendmail root < /tmp/docker-mailserver-test/email-templates/root-email.txt"
wait_for_empty_mail_queue_in_container mail
}
teardown_file() {
docker rm -f mail
}
#
# configuration checks
#
@test "checking configuration: user-patches.sh executed" {
run docker logs mail
assert_output --partial "Default user-patches.sh successfully executed"
}
@test "checking configuration: hostname/domainname" {
run docker run "${IMAGE_NAME:?}"
assert_success
}
#
# healthcheck
#
# NOTE: Healthcheck defaults an interval of 30 seconds
# If Postfix is temporarily down (eg: restart triggered by `check-for-changes.sh`),
# it may result in a false-positive `unhealthy` state.
# Be careful with re-locating this test if earlier tests could potentially fail it by
# triggering the `changedetector` service.
@test "checking container healthcheck" {
# ensure, that at least 30 seconds have passed since container start
while [[ "$(docker inspect --format='{{.State.Health.Status}}' mail)" == "starting" ]]; do
sleep 1
done
run docker inspect --format='{{.State.Health.Status}}' mail
assert_output "healthy"
assert_success
}
#
# processes
#
@test "checking process: postfix" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/lib/postfix/sbin/master'"
assert_success
}
@test "checking process: clamd (is not runnning)" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
assert_failure
}
@test "checking process: new" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/amavisd-new'"
assert_success
}
@test "checking process: opendkim" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/opendkim'"
assert_success
}
@test "checking process: opendmarc" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/opendmarc'"
assert_success
}
@test "checking process: fail2ban (disabled in default configuration)" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/bin/python3 /usr/bin/fail2ban-server'"
assert_failure
}
@test "checking process: fetchmail (disabled in default configuration)" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/bin/fetchmail'"
assert_failure
}
#
# imap
#
@test "checking process: dovecot imaplogin (enabled in default configuration)" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/dovecot'"
assert_success
}
@test "checking imap: server is ready with STARTTLS" {
run docker exec mail /bin/bash -c "nc -w 2 0.0.0.0 143 | grep '* OK' | grep 'STARTTLS' | grep 'ready'"
assert_success
}
@test "checking imap: authentication works" {
run docker exec mail /bin/sh -c "nc -w 1 0.0.0.0 143 < /tmp/docker-mailserver-test/auth/imap-auth.txt"
assert_success
}
@test "checking imap: added user authentication works" {
run docker exec mail /bin/sh -c "nc -w 1 0.0.0.0 143 < /tmp/docker-mailserver-test/auth/added-imap-auth.txt"
assert_success
}
#
# sasl
#
@test "checking sasl: doveadm auth test works with good password" {
run docker exec mail /bin/sh -c "doveadm auth test -x service=smtp user2@otherdomain.tld mypassword | grep 'auth succeeded'"
assert_success
}
@test "checking sasl: doveadm auth test fails with bad password" {
run docker exec mail /bin/sh -c "doveadm auth test -x service=smtp user2@otherdomain.tld BADPASSWORD | grep 'auth failed'"
assert_success
}
#
# logs
#
@test "checking logs: mail related logs should be located in a subdirectory" {
run docker exec mail /bin/sh -c "ls -1 /var/log/mail/ | grep -E 'mail.log'"
assert_success
}
#
# smtp
#
@test "checking smtp: authentication works with good password (plain)" {
run docker exec mail /bin/sh -c "nc -w 5 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/smtp-auth-plain.txt | grep 'Authentication successful'"
assert_success
}
@test "checking smtp: authentication fails with wrong password (plain)" {
run docker exec mail /bin/sh -c "nc -w 20 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/smtp-auth-plain-wrong.txt"
assert_output --partial 'authentication failed'
assert_success
}
@test "checking smtp: authentication works with good password (login)" {
run docker exec mail /bin/sh -c "nc -w 5 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/smtp-auth-login.txt | grep 'Authentication successful'"
assert_success
}
@test "checking smtp: authentication fails with wrong password (login)" {
run docker exec mail /bin/sh -c "nc -w 20 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/smtp-auth-login-wrong.txt"
assert_output --partial 'authentication failed'
assert_success
}
@test "checking smtp: added user authentication works with good password (plain)" {
run docker exec mail /bin/sh -c "nc -w 5 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/added-smtp-auth-plain.txt | grep 'Authentication successful'"
assert_success
}
@test "checking smtp: added user authentication fails with wrong password (plain)" {
run docker exec mail /bin/sh -c "nc -w 20 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/added-smtp-auth-plain-wrong.txt | grep 'authentication failed'"
assert_success
}
@test "checking smtp: added user authentication works with good password (login)" {
run docker exec mail /bin/sh -c "nc -w 5 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/added-smtp-auth-login.txt | grep 'Authentication successful'"
assert_success
}
@test "checking smtp: added user authentication fails with wrong password (login)" {
run docker exec mail /bin/sh -c "nc -w 20 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/added-smtp-auth-login-wrong.txt | grep 'authentication failed'"
assert_success
}
# TODO add a test covering case SPAMASSASSIN_SPAM_TO_INBOX=1 (default)
@test "checking smtp: delivers mail to existing account" {
run docker exec mail /bin/sh -c "grep 'postfix/lmtp' /var/log/mail/mail.log | grep 'status=sent' | grep ' Saved)' | sed 's/.* to=</</g' | sed 's/, relay.*//g' | sort | uniq -c | tr -s \" \""
assert_success
assert_output <<'EOF'
1 <added@localhost.localdomain>
6 <user1@localhost.localdomain>
1 <user1@localhost.localdomain>, orig_to=<postmaster@my-domain.com>
1 <user1@localhost.localdomain>, orig_to=<root>
1 <user1~test@localhost.localdomain>
2 <user2@otherdomain.tld>
1 <user3@localhost.localdomain>
EOF
}
@test "checking smtp: delivers mail to existing alias" {
run docker exec mail /bin/sh -c "grep 'to=<user1@localhost.localdomain>, orig_to=<alias1@localhost.localdomain>' /var/log/mail/mail.log | grep 'status=sent' | wc -l"
assert_success
assert_output 1
}
@test "checking smtp: delivers mail to existing alias with recipient delimiter" {
run docker exec mail /bin/sh -c "grep 'to=<user1~test@localhost.localdomain>, orig_to=<alias1~test@localhost.localdomain>' /var/log/mail/mail.log | grep 'status=sent' | wc -l"
assert_success
assert_output 1
run docker exec mail /bin/sh -c "grep 'to=<user1~test@localhost.localdomain>' /var/log/mail/mail.log | grep 'status=bounced'"
assert_failure
}
@test "checking smtp: delivers mail to existing catchall" {
run docker exec mail /bin/sh -c "grep 'to=<user1@localhost.localdomain>, orig_to=<wildcard@localdomain2.com>' /var/log/mail/mail.log | grep 'status=sent' | wc -l"
assert_success
assert_output 1
}
@test "checking smtp: delivers mail to regexp alias" {
run docker exec mail /bin/sh -c "grep 'to=<user1@localhost.localdomain>, orig_to=<test123@localhost.localdomain>' /var/log/mail/mail.log | grep 'status=sent' | wc -l"
assert_success
assert_output 1
}
@test "checking smtp: user1 should have received 9 mails" {
run docker exec mail /bin/sh -c "grep Subject /var/mail/localhost.localdomain/user1/new/* | sed 's/.*Subject: //g' | sed 's/\.txt.*//g' | sed 's/VIRUS.*/VIRUS/g' | sort"
assert_success
# 9 messages, the virus mail has three subject lines
cat <<'EOF' | assert_output
Root Test Message
Test Message amavis-virus
Test Message amavis-virus
Test Message existing-alias-external
Test Message existing-alias-recipient-delimiter
Test Message existing-catchall-local
Test Message existing-regexp-alias-local
Test Message existing-user-and-cc-local-alias
Test Message existing-user1
Test Message sieve-spam-folder
VIRUS
EOF
}
@test "checking smtp: rejects mail to unknown user" {
run docker exec mail /bin/sh -c "grep '<nouser@localhost.localdomain>: Recipient address rejected: User unknown in virtual mailbox table' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}
@test "checking smtp: redirects mail to external aliases" {
run docker exec mail /bin/sh -c "grep -- '-> <external1@otherdomain.tld>' /var/log/mail/mail.log* | grep RelayedInbound | wc -l"
assert_success
assert_output 2
}
# TODO add a test covering case SPAMASSASSIN_SPAM_TO_INBOX=1 (default)
@test "checking smtp: rejects spam" {
run docker exec mail /bin/sh -c "grep 'Blocked SPAM' /var/log/mail/mail.log | grep external.tld=spam@my-domain.com | wc -l"
assert_success
assert_output 1
}
@test "checking smtp: not advertising smtputf8" {
# Dovecot does not support SMTPUTF8, so while we can send we cannot receive
# Better disable SMTPUTF8 support entirely if we can't handle it correctly
run docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/smtp-ehlo.txt | grep SMTPUTF8 | wc -l"
assert_success
assert_output 0
}
#
# accounts
#
@test "checking accounts: user accounts" {
run docker exec mail doveadm user '*'
assert_success
assert_line --index 0 "user1@localhost.localdomain"
assert_line --index 1 "user2@otherdomain.tld"
assert_line --index 2 "user3@localhost.localdomain"
assert_line --index 3 "added@localhost.localdomain"
}
@test "checking accounts: user mail folder for user1" {
run docker exec mail /bin/bash -c "ls -d /var/mail/localhost.localdomain/user1"
assert_success
}
@test "checking accounts: user mail folder for user2" {
run docker exec mail /bin/bash -c "ls -d /var/mail/otherdomain.tld/user2"
assert_success
}
@test "checking accounts: user mail folder for user3" {
run docker exec mail /bin/bash -c "ls -d /var/mail/localhost.localdomain/user3/mail"
assert_success
}
@test "checking accounts: user mail folder for added user" {
run docker exec mail /bin/bash -c "ls -d /var/mail/localhost.localdomain/added"
assert_success
}
@test "checking accounts: comments are not parsed" {
run docker exec mail /bin/bash -c "ls /var/mail | grep 'comment'"
assert_failure
}
#
# postfix
#
@test "checking postfix: vhost file is correct" {
run docker exec mail cat /etc/postfix/vhost
assert_success
assert_line --index 0 "localdomain2.com"
assert_line --index 1 "localhost.localdomain"
assert_line --index 2 "otherdomain.tld"
}
@test "checking postfix: main.cf overrides" {
run docker exec mail grep -q 'max_idle = 600s' /tmp/docker-mailserver/postfix-main.cf
assert_success
run docker exec mail grep -q 'readme_directory = /tmp' /tmp/docker-mailserver/postfix-main.cf
assert_success
}
@test "checking postfix: master.cf overrides" {
run docker exec mail grep -q 'submission/inet/smtpd_sasl_security_options=noanonymous' /tmp/docker-mailserver/postfix-master.cf
assert_success
}
#
# dovecot
#
@test "checking dovecot: config additions" {
run docker exec mail grep -q 'mail_max_userip_connections = 69' /tmp/docker-mailserver/dovecot.cf
assert_success
run docker exec mail /bin/sh -c "doveconf | grep 'mail_max_userip_connections = 69'"
assert_success
assert_output 'mail_max_userip_connections = 69'
}
#
# spamassassin
#
@test "checking spamassassin: should be listed in amavis when enabled" {
run docker exec mail /bin/sh -c "grep -i 'ANTI-SPAM-SA code' /var/log/mail/mail.log | grep 'NOT loaded'"
assert_failure
}
@test "checking spamassassin: all registered domains should see spam headers" {
run docker exec mail /bin/sh -c "grep -ir 'X-Spam-' /var/mail/localhost.localdomain/user1/new"
assert_success
run docker exec mail /bin/sh -c "grep -ir 'X-Spam-' /var/mail/otherdomain.tld/user2/new"
assert_success
}
#
# postsrsd
#
@test "checking SRS: main.cf entries" {
run docker exec mail grep "sender_canonical_maps = tcp:localhost:10001" /etc/postfix/main.cf
assert_success
run docker exec mail grep "sender_canonical_classes = envelope_sender" /etc/postfix/main.cf
assert_success
run docker exec mail grep "recipient_canonical_maps = tcp:localhost:10002" /etc/postfix/main.cf
assert_success
run docker exec mail grep "recipient_canonical_classes = envelope_recipient,header_recipient" /etc/postfix/main.cf
assert_success
}
@test "checking SRS: postsrsd running" {
run docker exec mail /bin/sh -c "ps aux | grep ^postsrsd"
assert_success
}
@test "checking SRS: fallback to hostname is handled correctly" {
run docker exec mail grep "SRS_DOMAIN=my-domain.com" /etc/default/postsrsd
assert_success
}
#
# system
#
@test "checking system: freshclam cron is disabled" {
run docker exec mail bash -c "grep '/usr/bin/freshclam' -r /etc/cron.d"
assert_failure
}
@test "checking amavis: virusmail wiper cron exists" {
run docker exec mail bash -c "crontab -l | grep '/usr/local/bin/virus-wiper'"
assert_success
}
@test "checking amavis: VIRUSMAILS_DELETE_DELAY override works as expected" {
# shellcheck disable=SC2016
run docker run --rm -e VIRUSMAILS_DELETE_DELAY=2 "${IMAGE_NAME:?}" /bin/bash -c 'echo "${VIRUSMAILS_DELETE_DELAY}"'
assert_output 2
}
@test "checking amavis: old virusmail is wipped by cron" {
docker exec mail bash -c 'touch -d "`date --date=2000-01-01`" /var/lib/amavis/virusmails/should-be-deleted'
run docker exec mail bash -c '/usr/local/bin/virus-wiper'
assert_success
run docker exec mail bash -c 'ls -la /var/lib/amavis/virusmails/ | grep should-be-deleted'
assert_failure
}
@test "checking amavis: recent virusmail is not wipped by cron" {
docker exec mail bash -c 'touch -d "`date`" /var/lib/amavis/virusmails/should-not-be-deleted'
run docker exec mail bash -c '/usr/local/bin/virus-wiper'
assert_success
run docker exec mail bash -c 'ls -la /var/lib/amavis/virusmails/ | grep should-not-be-deleted'
assert_success
}
@test "checking system: /var/log/mail/mail.log is error free" {
run docker exec mail grep 'non-null host address bits in' /var/log/mail/mail.log
assert_failure
run docker exec mail grep 'mail system configuration error' /var/log/mail/mail.log
assert_failure
run docker exec mail grep ': error:' /var/log/mail/mail.log
assert_failure
run docker exec mail grep -i 'is not writable' /var/log/mail/mail.log
assert_failure
run docker exec mail grep -i 'permission denied' /var/log/mail/mail.log
assert_failure
run docker exec mail grep -i '(!)connect' /var/log/mail/mail.log
assert_failure
run docker exec mail grep -i 'using backwards-compatible default setting' /var/log/mail/mail.log
assert_failure
run docker exec mail grep -i 'connect to 127.0.0.1:10023: Connection refused' /var/log/mail/mail.log
assert_failure
}
@test "checking system: /var/log/auth.log is error free" {
run docker exec mail grep 'Unable to open env file: /etc/default/locale' /var/log/auth.log
assert_failure
}
@test "checking system: sets the server fqdn" {
run docker exec mail hostname
assert_success
assert_output "mail.my-domain.com"
}
@test "checking system: sets the server domain name in /etc/mailname" {
run docker exec mail cat /etc/mailname
assert_success
assert_output "my-domain.com"
}
@test "checking system: postfix should not log to syslog" {
run docker exec mail grep 'postfix' /var/log/syslog
assert_failure
}
@test "checking system: amavis decoders installed and available" {
run docker exec mail /bin/sh -c "grep -E '.*(Internal decoder|Found decoder) for\s+\..*' /var/log/mail/mail.log*|grep -Eo '(mail|Z|gz|bz2|xz|lzma|lrz|lzo|lz4|rpm|cpio|tar|deb|rar|arj|arc|zoo|doc|cab|tnef|zip|kmz|7z|jar|swf|lha|iso|exe)' | sort | uniq"
assert_success
# Support for doc and zoo removed in buster
cat <<'EOF' | assert_output
7z
Z
arc
arj
bz2
cab
cpio
deb
exe
gz
iso
jar
kmz
lha
lrz
lz4
lzma
lzo
mail
rar
rpm
swf
tar
tnef
xz
zip
EOF
}
#
# sieve
#
@test "checking sieve: user1 should have received 1 email in folder INBOX.spam" {
run docker exec mail /bin/sh -c "ls -A /var/mail/localhost.localdomain/user1/.INBOX.spam/new | wc -l"
assert_success
assert_output 1
}
@test "checking manage sieve: server is ready when ENABLE_MANAGESIEVE has been set" {
run docker exec mail /bin/bash -c "nc -z 0.0.0.0 4190"
assert_success
}
@test "checking sieve: user2 should have piped 1 email to /tmp/" {
run docker exec mail /bin/sh -c "ls -A /tmp/pipe-test.out | wc -l"
assert_success
assert_output 1
}
@test "checking sieve global: user1 should have gotten a copy of his spam mail" {
run docker exec mail /bin/sh -c "grep 'Spambot <spam@spam.com>' -R /var/mail/localhost.localdomain/user1/new/"
assert_success
}
#
# accounts
#
@test "checking accounts: user_without_domain creation should be rejected since user@domain format is required" {
run docker exec mail /bin/sh -c "addmailuser user_without_domain mypassword"
assert_failure
assert_output --partial 'should include the domain (eg: user@example.com)'
}
@test "checking accounts: user3 should have been added to /tmp/docker-mailserver/postfix-accounts.cf" {
docker exec mail /bin/sh -c "addmailuser user3@domain.tld mypassword"
run docker exec mail /bin/sh -c "grep '^user3@domain\.tld|' -i /tmp/docker-mailserver/postfix-accounts.cf"
assert_success
[[ -n ${output} ]]
}
@test "checking accounts: auser3 should have been added to /tmp/docker-mailserver/postfix-accounts.cf" {
docker exec mail /bin/sh -c "addmailuser auser3@domain.tld mypassword"
run docker exec mail /bin/sh -c "grep '^auser3@domain\.tld|' -i /tmp/docker-mailserver/postfix-accounts.cf"
assert_success
[[ -n ${output} ]]
}
@test "checking accounts: a.ser3 should have been added to /tmp/docker-mailserver/postfix-accounts.cf" {
docker exec mail /bin/sh -c "addmailuser a.ser3@domain.tld mypassword"
run docker exec mail /bin/sh -c "grep '^a\.ser3@domain\.tld|' -i /tmp/docker-mailserver/postfix-accounts.cf"
assert_success
[[ -n ${output} ]]
}
@test "checking accounts: user3 should have been removed from /tmp/docker-mailserver/postfix-accounts.cf but not auser3" {
wait_until_account_maildir_exists mail 'user3@domain.tld'
docker exec mail /bin/sh -c "delmailuser -y user3@domain.tld"
run docker exec mail /bin/sh -c "grep '^user3@domain\.tld' -i /tmp/docker-mailserver/postfix-accounts.cf"
assert_failure
[[ -z ${output} ]]
run docker exec mail /bin/sh -c "grep '^auser3@domain\.tld' -i /tmp/docker-mailserver/postfix-accounts.cf"
assert_success
[[ -n ${output} ]]
}
@test "checking user updating password for user in /tmp/docker-mailserver/postfix-accounts.cf" {
add_mail_account_then_wait_until_ready mail 'user4@domain.tld'
initialpass=$(docker exec mail /bin/sh -c "grep '^user4@domain\.tld' -i /tmp/docker-mailserver/postfix-accounts.cf")
sleep 2
docker exec mail /bin/sh -c "updatemailuser user4@domain.tld mynewpassword"
sleep 2
changepass=$(docker exec mail /bin/sh -c "grep '^user4@domain\.tld' -i /tmp/docker-mailserver/postfix-accounts.cf")
[[ ${initialpass} != "${changepass}" ]]
run docker exec mail /bin/sh -c "delmailuser -y auser3@domain.tld"
assert_success
}
@test "checking accounts: listmailuser (quotas disabled)" {
run docker exec mail /bin/sh -c "echo 'ENABLE_QUOTAS=0' >> /etc/dms-settings && listmailuser | head -n 1"
assert_success
assert_output '* user1@localhost.localdomain'
}
@test "checking accounts: listmailuser (quotas enabled)" {
run docker exec mail /bin/sh -c "sed -i '/ENABLE_QUOTAS=0/d' /etc/dms-settings; listmailuser | head -n 1"
assert_success
assert_output '* user1@localhost.localdomain ( 10K / ~ ) [0%]'
}
@test "checking accounts: no error is generated when deleting a user if /tmp/docker-mailserver/postfix-accounts.cf is missing" {
run docker run --rm \
-v "$(duplicate_config_for_container without-accounts/ without-accounts-deleting-user)":/tmp/docker-mailserver/ \
"${IMAGE_NAME:?}" /bin/sh -c 'delmailuser -y user3@domain.tld'
assert_success
[[ -z ${output} ]]
}
@test "checking accounts: user3 should have been added to /tmp/docker-mailserver/postfix-accounts.cf even when that file does not exist" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container without-accounts/ without-accounts_file_does_not_exist)
run docker run --rm \
-v "${PRIVATE_CONFIG}/without-accounts/":/tmp/docker-mailserver/ \
"${IMAGE_NAME:?}" /bin/sh -c 'addmailuser user3@domain.tld mypassword'
assert_success
run docker run --rm \
-v "${PRIVATE_CONFIG}/without-accounts/":/tmp/docker-mailserver/ \
"${IMAGE_NAME:?}" /bin/sh -c 'grep user3@domain.tld -i /tmp/docker-mailserver/postfix-accounts.cf'
assert_success
[[ -n ${output} ]]
}
@test "checking quota: setquota user must be existing" {
add_mail_account_then_wait_until_ready mail 'quota_user@domain.tld'
run docker exec mail /bin/sh -c "setquota quota_user 50M"
assert_failure
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld 50M"
assert_success
run docker exec mail /bin/sh -c "setquota username@fulldomain 50M"
assert_failure
run docker exec mail /bin/sh -c "delmailuser -y quota_user@domain.tld"
assert_success
}
@test "checking quota: setquota <quota> must be well formatted" {
add_mail_account_then_wait_until_ready mail 'quota_user@domain.tld'
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld 26GIGOTS"
assert_failure
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld 123"
assert_failure
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld M"
assert_failure
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld -60M"
assert_failure
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld 10B"
assert_success
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld 10k"
assert_success
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld 10M"
assert_success
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld 10G"
assert_success
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld 10T"
assert_success
run docker exec mail /bin/sh -c "delmailuser -y quota_user@domain.tld"
assert_success
}
@test "checking quota: delquota user must be existing" {
add_mail_account_then_wait_until_ready mail 'quota_user@domain.tld'
run docker exec mail /bin/sh -c "delquota uota_user@domain.tld"
assert_failure
run docker exec mail /bin/sh -c "delquota quota_user"
assert_failure
run docker exec mail /bin/sh -c "delquota dontknowyou@domain.tld"
assert_failure
run docker exec mail /bin/sh -c "setquota quota_user@domain.tld 10T"
assert_success
run docker exec mail /bin/sh -c "delquota quota_user@domain.tld"
assert_success
run docker exec mail /bin/sh -c "grep -i 'quota_user@domain.tld' /tmp/docker-mailserver/dovecot-quotas.cf"
assert_failure
run docker exec mail /bin/sh -c "delmailuser -y quota_user@domain.tld"
assert_success
}
@test "checking quota: delquota allow when no quota for existing user" {
add_mail_account_then_wait_until_ready mail 'quota_user@domain.tld'
run docker exec mail /bin/sh -c "grep -i 'quota_user@domain.tld' /tmp/docker-mailserver/dovecot-quotas.cf"
assert_failure
run docker exec mail /bin/sh -c "delquota quota_user@domain.tld"
assert_success
run docker exec mail /bin/sh -c "delquota quota_user@domain.tld"
assert_success
run docker exec mail /bin/sh -c "delmailuser -y quota_user@domain.tld"
assert_success
}
@test "checking quota: dovecot quota present in postconf" {
run docker exec mail /bin/bash -c "postconf | grep 'check_policy_service inet:localhost:65265'"
assert_success
}
@test "checking quota: dovecot mailbox max size must be equal to postfix mailbox max size" {
postfix_mailbox_size=$(docker exec mail sh -c "postconf | grep -Po '(?<=mailbox_size_limit = )[0-9]+'")
run echo "${postfix_mailbox_size}"
refute_output ""
# dovecot relies on virtual_mailbox_size by default
postfix_virtual_mailbox_size=$(docker exec mail sh -c "postconf | grep -Po '(?<=virtual_mailbox_limit = )[0-9]+'")
assert_equal "${postfix_virtual_mailbox_size}" "${postfix_mailbox_size}"
postfix_mailbox_size_mb=$(( postfix_mailbox_size / 1000000))
dovecot_mailbox_size_mb=$(docker exec mail sh -c "doveconf | grep -oP '(?<=quota_rule \= \*\:storage=)[0-9]+'")
run echo "${dovecot_mailbox_size_mb}"
refute_output ""
assert_equal "${postfix_mailbox_size_mb}" "${dovecot_mailbox_size_mb}"
}
@test "checking quota: dovecot message max size must be equal to postfix messsage max size" {
postfix_message_size=$(docker exec mail sh -c "postconf | grep -Po '(?<=message_size_limit = )[0-9]+'")
run echo "${postfix_message_size}"
refute_output ""
postfix_message_size_mb=$(( postfix_message_size / 1000000))
dovecot_message_size_mb=$(docker exec mail sh -c "doveconf | grep -oP '(?<=quota_max_mail_size = )[0-9]+'")
run echo "${dovecot_message_size_mb}"
refute_output ""
assert_equal "${postfix_message_size_mb}" "${dovecot_message_size_mb}"
}
@test "checking quota: quota directive is removed when mailbox is removed" {
add_mail_account_then_wait_until_ready mail 'quserremoved@domain.tld'
run docker exec mail /bin/sh -c "setquota quserremoved@domain.tld 12M"
assert_success
run docker exec mail /bin/sh -c 'cat /tmp/docker-mailserver/dovecot-quotas.cf | grep -E "^quserremoved@domain.tld\:12M\$" | wc -l | grep 1'
assert_success
run docker exec mail /bin/sh -c "delmailuser -y quserremoved@domain.tld"
assert_success
run docker exec mail /bin/sh -c 'cat /tmp/docker-mailserver/dovecot-quotas.cf | grep -E "^quserremoved@domain.tld\:12M\$"'
assert_failure
}
@test "checking quota: dovecot applies user quota" {
run docker exec mail /bin/sh -c "doveadm quota get -u 'user1@localhost.localdomain' | grep 'User quota STORAGE'"
assert_output --partial "- 0"
run docker exec mail /bin/sh -c "setquota user1@localhost.localdomain 50M"
assert_success
# wait until quota has been updated
run repeat_until_success_or_timeout 20 sh -c "docker exec mail sh -c 'doveadm quota get -u user1@localhost.localdomain | grep -oP \"(User quota STORAGE\s+[0-9]+\s+)51200(.*)\"'"
assert_success
run docker exec mail /bin/sh -c "delquota user1@localhost.localdomain"
assert_success
# wait until quota has been updated
run repeat_until_success_or_timeout 20 sh -c "docker exec mail sh -c 'doveadm quota get -u user1@localhost.localdomain | grep -oP \"(User quota STORAGE\s+[0-9]+\s+)-(.*)\"'"
assert_success
}
@test "checking quota: warn message received when quota exceeded" {
skip 'disabled as it fails randomly: https://github.com/docker-mailserver/docker-mailserver/pull/2511'
# create user
add_mail_account_then_wait_until_ready mail 'quotauser@otherdomain.tld'
run docker exec mail /bin/sh -c 'setquota quotauser@otherdomain.tld 10k'
assert_success
# wait until quota has been updated
run repeat_until_success_or_timeout 20 sh -c "docker exec mail sh -c 'doveadm quota get -u quotauser@otherdomain.tld | grep -oP \"(User quota STORAGE\s+[0-9]+\s+)10(.*)\"'"
assert_success
# dovecot and postfix has been restarted
wait_for_service mail postfix
wait_for_service mail dovecot
sleep 10
# send some big emails
run docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/quota-exceeded.txt"
assert_success
run docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/quota-exceeded.txt"
assert_success
run docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/quota-exceeded.txt"
assert_success
# check for quota warn message existence
run repeat_until_success_or_timeout 20 sh -c "docker exec mail sh -c 'grep \"Subject: quota warning\" /var/mail/otherdomain.tld/quotauser/new/ -R'"
assert_success
run repeat_until_success_or_timeout 20 sh -c "docker logs mail | grep 'Quota exceeded (mailbox for user is full)'"
assert_success
# ensure only the first big message and the warn message are present (other messages are rejected: mailbox is full)
run docker exec mail sh -c 'ls /var/mail/otherdomain.tld/quotauser/new/ | wc -l'
assert_success
assert_output "2"
run docker exec mail /bin/sh -c "delmailuser -y quotauser@otherdomain.tld"
assert_success
}
#
# PERMIT_DOCKER mynetworks
#
@test "checking PERMIT_DOCKER: can get container ip" {
run docker exec mail /bin/sh -c "ip addr show eth0 | grep 'inet ' | sed 's/[^0-9\.\/]*//g' | cut -d '/' -f 1 | egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}'"
assert_success
}
@test "checking PERMIT_DOCKER: my network value" {
run docker exec mail /bin/sh -c "postconf | grep '^mynetworks =' | egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.0\.0/16'"
assert_success
}
#
# amavis
#
@test "checking amavis: config overrides" {
run docker exec mail /bin/sh -c "grep 'Test Verification' /etc/amavis/conf.d/50-user | wc -l"
assert_success
assert_output 1
}
# TODO investigate why this test fails
@test "checking user login: predefined user can login" {
skip 'disabled as it fails randomly: https://github.com/docker-mailserver/docker-mailserver/pull/2177'
run docker exec mail /bin/bash -c "doveadm auth test -x service=smtp pass@localhost.localdomain 'may be \\a \`p^a.*ssword' | grep 'passdb'"
assert_output "passdb: pass@localhost.localdomain auth succeeded"
}
#
# LDAP
#
# postfix
@test "checking dovecot: postmaster address" {
run docker exec mail /bin/sh -c "grep 'postmaster_address = postmaster@my-domain.com' /etc/dovecot/conf.d/15-lda.conf"
assert_success
}
@test "checking spoofing: rejects sender forging" {
# checking rejection of spoofed sender
wait_for_smtp_port_in_container_to_respond mail
run docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/added-smtp-auth-spoofed.txt"
assert_output --partial 'Sender address rejected: not owned by user'
}
@test "checking spoofing: accepts sending as alias" {
run docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/auth/added-smtp-auth-spoofed-alias.txt | grep 'End data with'"
assert_success
}
#
# Pflogsumm delivery check
#
@test "checking pflogsum delivery" {
# checking logrotation working and report being sent
docker exec mail logrotate --force /etc/logrotate.d/maillog
sleep 10
run docker exec mail grep "Subject: Postfix Summary for " /var/mail/localhost.localdomain/user1/new/ -R
assert_success
# check sender is the one specified in REPORT_SENDER
run docker exec mail grep "From: report1@mail.my-domain.com" /var/mail/localhost.localdomain/user1/new/ -R
assert_success
# check sender is not the default one.
run docker exec mail grep "From: mailserver-report@mail.my-domain.com" /var/mail/localhost.localdomain/user1/new/ -R
assert_failure
}
#
# supervisor
#
@test "checking restart of process: postfix" {
run docker exec mail /bin/bash -c "pkill master && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/lib/postfix/sbin/master'"
assert_success
}
@test "checking restart of process: amavisd-new" {
run docker exec mail /bin/bash -c "pkill amavi && sleep 12 && ps aux --forest | grep -v grep | grep '/usr/sbin/amavisd-new (master)'"
assert_success
}
@test "checking restart of process: opendkim" {
run docker exec mail /bin/bash -c "pkill opendkim && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/opendkim'"
assert_success
}
@test "checking restart of process: opendmarc" {
run docker exec mail /bin/bash -c "pkill opendmarc && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/opendmarc'"
assert_success
}
#
# root mail delivery
#
@test "checking that mail for root was delivered" {
run docker exec mail grep "Subject: Root Test Message" /var/mail/localhost.localdomain/user1/new/ -R
assert_success
}