Merge branch 'v2' of github.com:tomav/docker-mailserver into v2

This commit is contained in:
Thomas VIAL 2016-04-11 22:09:12 +02:00
commit ad5536057a
49 changed files with 3128 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.DS_Store
docker-compose.yml
postfix/ssl/*
letsencrypt/
.idea

6
.travis.yml Normal file
View file

@ -0,0 +1,6 @@
language: bash
sudo: required
services:
- docker
script:
- make all

69
Dockerfile Normal file
View file

@ -0,0 +1,69 @@
FROM ubuntu:14.04
MAINTAINER Thomas VIAL
# Packages
RUN apt-get update -q --fix-missing
RUN apt-get -y upgrade
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install vim postfix dovecot-core dovecot-imapd dovecot-pop3d \
supervisor gamin amavisd-new spamassassin clamav clamav-daemon libnet-dns-perl libmail-spf-perl \
pyzor razor arj bzip2 cabextract cpio file gzip nomarch p7zip pax unzip zip zoo rsyslog mailutils netcat \
opendkim opendkim-tools opendmarc curl fail2ban
RUN apt-get autoclean && rm -rf /var/lib/apt/lists/*
# Configures Dovecot
RUN sed -i -e 's/include_try \/usr\/share\/dovecot\/protocols\.d/include_try \/etc\/dovecot\/protocols\.d/g' /etc/dovecot/dovecot.conf
ADD dovecot/auth-passwdfile.inc /etc/dovecot/conf.d/
ADD dovecot/10-*.conf /etc/dovecot/conf.d/
# Enables Spamassassin and CRON updates
RUN sed -i -r 's/^(CRON|ENABLED)=0/\1=1/g' /etc/default/spamassassin
# Enables Amavis
RUN sed -i -r 's/#(@| \\%)bypass/\1bypass/g' /etc/amavis/conf.d/15-content_filter_mode
RUN adduser clamav amavis
RUN adduser amavis clamav
RUN useradd -u 5000 -d /home/docker -s /bin/bash -p $(echo docker | openssl passwd -1 -stdin) docker
# Enables Clamav
RUN chmod 644 /etc/clamav/freshclam.conf
RUN (crontab -l ; echo "0 1 * * * /usr/bin/freshclam --quiet") | sort - | uniq - | crontab -
RUN freshclam
# Configure DKIM (opendkim)
RUN mkdir -p /etc/opendkim/keys
ADD postfix/TrustedHosts /etc/opendkim/TrustedHosts
# DKIM config files
ADD postfix/opendkim.conf /etc/opendkim.conf
ADD postfix/default-opendkim /etc/default/opendkim
# Configure DMARC (opendmarc)
ADD postfix/opendmarc.conf /etc/opendmarc.conf
ADD postfix/default-opendmarc /etc/default/opendmarc
# Configures Postfix
ADD postfix/main.cf /etc/postfix/main.cf
ADD postfix/master.cf /etc/postfix/master.cf
ADD bin/generate-ssl-certificate /usr/local/bin/generate-ssl-certificate
RUN chmod +x /usr/local/bin/generate-ssl-certificate
# Get LetsEncrypt signed certificate
RUN curl https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem > /etc/ssl/certs/lets-encrypt-x1-cross-signed.pem
RUN curl https://letsencrypt.org/certs/lets-encrypt-x2-cross-signed.pem > /etc/ssl/certs/lets-encrypt-x2-cross-signed.pem
# Start-mailserver script
ADD start-mailserver.sh /usr/local/bin/start-mailserver.sh
RUN chmod +x /usr/local/bin/start-mailserver.sh
# SMTP ports
EXPOSE 25
EXPOSE 587
# IMAP ports
EXPOSE 143
EXPOSE 993
# POP3 ports
EXPOSE 110
EXPOSE 995
CMD /usr/local/bin/start-mailserver.sh

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Thomas VIAL
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

68
Makefile Normal file
View file

@ -0,0 +1,68 @@
NAME = tvial/docker-mailserver:dovecot
#all: build run fixtures tests clean
#all-no-build: run fixtures tests clean
all: build
build:
docker build --no-cache -t $(NAME) .
run:
# Copy test files
cp test/accounts.cf postfix/
cp test/main.cf postfix/
cp test/virtual postfix/
# Run containers
docker run -d --name mail \
-v "`pwd`/postfix":/tmp/postfix \
-v "`pwd`/spamassassin":/tmp/spamassassin \
-v "`pwd`/test":/tmp/test \
-e SA_TAG=1.0 \
-e SA_TAG2=2.0 \
-e SA_KILL=3.0 \
-e SASL_PASSWD=testing \
-h mail.my-domain.com -t $(NAME)
sleep 15
docker run -d --name mail_pop3 \
-v "`pwd`/postfix":/tmp/postfix \
-v "`pwd`/spamassassin":/tmp/spamassassin \
-v "`pwd`/test":/tmp/test \
-e ENABLE_POP3=1 \
-h mail.my-domain.com -t $(NAME)
sleep 15
docker run -d --name mail_smtponly \
-v "`pwd`/postfix":/tmp/postfix \
-v "`pwd`/spamassassin":/tmp/spamassassin \
-v "`pwd`/test":/tmp/test \
-e SMTP_ONLY=1 \
-h mail.my-domain.com -t $(NAME)
sleep 15
docker run -d --name mail_fail2ban \
-v "`pwd`/postfix":/tmp/postfix \
-v "`pwd`/spamassassin":/tmp/spamassassin \
-v "`pwd`/test":/tmp/test \
-e ENABLE_FAIL2BAN=1 \
-h mail.my-domain.com -t $(NAME)
# Wait for containers to fully start
sleep 15
fixtures:
# Sending test mails
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/test/email-templates/amavis-spam.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/test/email-templates/amavis-virus.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/test/email-templates/existing-alias-external.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/test/email-templates/existing-alias-local.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/test/email-templates/existing-user.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/test/email-templates/non-existing-user.txt"
# Wait for mails to be analyzed
sleep 10
tests:
# Start tests
./test/bats/bats test/tests.bats
clean:
# Get default files back
git checkout postfix/accounts.cf postfix/main.cf postfix/virtual
# Remove running test containers
docker rm -f mail mail_pop3 mail_smtponly mail_fail2ban

134
README.md Normal file
View file

@ -0,0 +1,134 @@
# docker-mailserver
[![Build Status](https://travis-ci.org/tomav/docker-mailserver.svg?branch=master)](https://travis-ci.org/tomav/docker-mailserver)
A fullstack but simple mail server (smtp, imap, antispam, antivirus...).
Only configuration files, no SQL database. Keep it simple and versioned.
Easy to deploy and upgrade.
Includes:
- postfix with smtp auth
- dovecot for sasl, imap (and optional pop3) with ssl support
- amavis
- spamassasin supporting custom rules
- clamav with automatic updates
- opendkim
- opendmarc
- fail2ban
- [LetsEncrypt](https://letsencrypt.org/) and self-signed certificates
- [integration tests](https://travis-ci.org/tomav/docker-mailserver)
- [automated builds on docker hub](https://hub.docker.com/r/tvial/docker-mailserver/)
Why I created this image: [Simple mail server with Docker](http://tvi.al/simple-mail-server-with-docker/)
Before you open an issue, please have a look this `README`, the [FAQ](https://github.com/tomav/docker-mailserver/wiki/FAQ) and Postfix documentation.
## Usage
# get latest image
docker pull tvial/docker-mailserver
# create a "docker-compose.yml" file containing:
mail:
image: tvial/docker-mailserver
hostname: mail
domainname: domain.com
# your FQDN will be 'mail.domain.com'
ports:
- "25:25"
- "143:143"
- "587:587"
- "993:993"
volumes:
- ./spamassassin:/tmp/spamassassin/
- ./postfix:/tmp/postfix/
# start the container
docker-compose up -d mail
## Managing users and aliases
### Users
Users are managed in `postfix/accounts.cf`.
Just add the full email address and its encrypted password separated by a pipe.
Example:
user1@domain.tld|mypassword-encrypted
user2@otherdomain.tld|myotherpassword-encrypted
To generate the password you could run for example the following:
docker run --rm -ti tvial/docker-mailserver doveadm pw -s MD5-CRYPT -u user1@domain.tld
You will be asked for a password (and for a confirmation of the password). Just copy all the output string in the file `postfix/accounts.cf`.
The `doveadm pw` command let you choose between several encryption schemes for the password.
Use doveadm pw -l to get a list of the currently supported encryption schemes.
### Aliases
Please first read [Postfix documentation on virtual aliases](http://www.postfix.org/VIRTUAL_README.html#virtual_alias).
Aliases are managed in `postfix/virtual`.
An alias is a full email address that will be:
* delivered to an existing account in `postfix/accounts.cf`
* redirected to one or more other email adresses
Alias and target are space separated.
Example:
# Alias to existing account
alias1@domain.tld user1@domain.tld
# Forward to external email address
alias2@domain.tld external@gmail.com
## Environment variables
* DMS_SSL
* *empty* (default) => SSL disabled
* letsencrypt => Enables Let's Encrypt certificates
* self-signed => Enables self-signed certificates
* ENABLE_POP3
* *empty* (default) => POP3 service disabled
* 1 => Enables POP3 service
* SMTP_ONLY
* *empty* (default) => courier daemons might start
* *1 => do not launch any courier daemons (imap, pop3)
* SA_TAG
* *2.0* (default) => add spam info headers if at, or above that level
* SA_TAG2
* *6.31* (default) => add 'spam detected' headers at that level
* SA_KILL
* *6.31* (default) => triggers spam evasive actions
* SASL_PASSWORD
* *empty* (default) => No sasl_passwd will be created
* *string* => A /etc/postfix/sasl_passwd will be created with that content and postmap will be run on it
* ENABLE_FAIL2BAN
* *empty* (default) => fail2ban service disabled
* 1 => Enables fail2ban service
Please read [how the container starts](https://github.com/tomav/docker-mailserver/blob/master/start-mailserver.sh) to understand what's expected.
## SSL
Please read [the SSL page in the wiki](https://github.com/tomav/docker-mailserver/wiki/SSL) for more information.
## Todo
Things to do or to improve are stored on [Github](https://github.com/tomav/docker-mailserver/issues), some open by myself.
Feel free to improve this docker image.
## Contribute
- Fork
- Improve
- Add integration tests in `test/tests.bats`
- Build image and run tests using `make`
- Document your improvements
- Commit, push and make a pull-request

View file

@ -0,0 +1,14 @@
#!/bin/sh
FQDN=$(hostname)
cd /ssl
# Create CA certificate
/usr/lib/ssl/misc/CA.pl -newca
# Create an unpassworded private key and create an unsigned public key certificate
openssl req -new -nodes -keyout /ssl/$FQDN-key.pem -out /ssl/$FQDN-req.pem -days 3652
# Sign the public key certificate with CA certificate
openssl ca -out /ssl/$FQDN-cert.pem -infiles /ssl/$FQDN-req.pem
# Combine certificates for courier
cat /ssl/$FQDN-key.pem /ssl/$FQDN-cert.pem > /ssl/$FQDN-combined.pem

15
docker-compose.yml.dist Normal file
View file

@ -0,0 +1,15 @@
mail:
image: tvial/docker-mailserver
hostname: mail
domainname: domain.com
cap_add:
- NET_ADMIN
ports:
- "25:25"
- "143:143"
- "587:587"
- "993:993"
volumes:
- ./spamassassin:/tmp/spamassassin/
- ./postfix:/tmp/postfix/
environment:

128
dovecot/10-auth.conf Normal file
View file

@ -0,0 +1,128 @@
##
## Authentication processes
##
# Disable LOGIN command and all other plaintext authentications unless
# SSL/TLS is used (LOGINDISABLED capability). Note that if the remote IP
# matches the local IP (ie. you're connecting from the same computer), the
# connection is considered secure and plaintext authentication is allowed.
# See also ssl=required setting.
#disable_plaintext_auth = yes
# Authentication cache size (e.g. 10M). 0 means it's disabled. Note that
# bsdauth, PAM and vpopmail require cache_key to be set for caching to be used.
#auth_cache_size = 0
# Time to live for cached data. After TTL expires the cached record is no
# longer used, *except* if the main database lookup returns internal failure.
# We also try to handle password changes automatically: If user's previous
# authentication was successful, but this one wasn't, the cache isn't used.
# For now this works only with plaintext authentication.
#auth_cache_ttl = 1 hour
# TTL for negative hits (user not found, password mismatch).
# 0 disables caching them completely.
#auth_cache_negative_ttl = 1 hour
# Space separated list of realms for SASL authentication mechanisms that need
# them. You can leave it empty if you don't want to support multiple realms.
# Many clients simply use the first one listed here, so keep the default realm
# first.
#auth_realms =
# Default realm/domain to use if none was specified. This is used for both
# SASL realms and appending @domain to username in plaintext logins.
#auth_default_realm =
# List of allowed characters in username. If the user-given username contains
# a character not listed in here, the login automatically fails. This is just
# an extra check to make sure user can't exploit any potential quote escaping
# vulnerabilities with SQL/LDAP databases. If you want to allow all characters,
# set this value to empty.
#auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
# Username character translations before it's looked up from databases. The
# value contains series of from -> to characters. For example "#@/@" means
# that '#' and '/' characters are translated to '@'.
#auth_username_translation =
# Username formatting before it's looked up from databases. You can use
# the standard variables here, eg. %Lu would lowercase the username, %n would
# drop away the domain if it was given, or "%n-AT-%d" would change the '@' into
# "-AT-". This translation is done after auth_username_translation changes.
#auth_username_format = %Lu
# If you want to allow master users to log in by specifying the master
# username within the normal username string (ie. not using SASL mechanism's
# support for it), you can specify the separator character here. The format
# is then <username><separator><master username>. UW-IMAP uses "*" as the
# separator, so that could be a good choice.
#auth_master_user_separator =
# Username to use for users logging in with ANONYMOUS SASL mechanism
#auth_anonymous_username = anonymous
# Maximum number of dovecot-auth worker processes. They're used to execute
# blocking passdb and userdb queries (eg. MySQL and PAM). They're
# automatically created and destroyed as needed.
#auth_worker_max_count = 30
# Host name to use in GSSAPI principal names. The default is to use the
# name returned by gethostname(). Use "$ALL" (with quotes) to allow all keytab
# entries.
#auth_gssapi_hostname =
# Kerberos keytab to use for the GSSAPI mechanism. Will use the system
# default (usually /etc/krb5.keytab) if not specified. You may need to change
# the auth service to run as root to be able to read this file.
#auth_krb5_keytab =
# Do NTLM and GSS-SPNEGO authentication using Samba's winbind daemon and
# ntlm_auth helper. <doc/wiki/Authentication/Mechanisms/Winbind.txt>
#auth_use_winbind = no
# Path for Samba's ntlm_auth helper binary.
#auth_winbind_helper_path = /usr/bin/ntlm_auth
# Time to delay before replying to failed authentications.
#auth_failure_delay = 2 secs
# Require a valid SSL client certificate or the authentication fails.
#auth_ssl_require_client_cert = no
# Take the username from client's SSL certificate, using
# X509_NAME_get_text_by_NID() which returns the subject's DN's
# CommonName.
#auth_ssl_username_from_cert = no
# Space separated list of wanted authentication mechanisms:
# plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey
# gss-spnego
# NOTE: See also disable_plaintext_auth setting.
auth_mechanisms = plain login
##
## Password and user databases
##
#
# Password database is used to verify user's password (and nothing more).
# You can have multiple passdbs and userdbs. This is useful if you want to
# allow both system users (/etc/passwd) and virtual users to login without
# duplicating the system users into virtual database.
#
# <doc/wiki/PasswordDatabase.txt>
#
# User database specifies where mails are located and what user/group IDs
# own them. For single-UID configuration use "static" userdb.
#
# <doc/wiki/UserDatabase.txt>
#!include auth-deny.conf.ext
#!include auth-master.conf.ext
#!include auth-system.conf.ext
#!include auth-sql.conf.ext
#!include auth-ldap.conf.ext
!include auth-passwdfile.inc
#!include auth-checkpassword.conf.ext
#!include auth-vpopmail.conf.ext
#!include auth-static.conf.ext

84
dovecot/10-logging.conf Normal file
View file

@ -0,0 +1,84 @@
##
## Log destination.
##
# Log file to use for error messages. "syslog" logs to syslog,
# /dev/stderr logs to stderr.
#log_path = syslog
# Log file to use for informational messages. Defaults to log_path.
#info_log_path =
# Log file to use for debug messages. Defaults to info_log_path.
#debug_log_path =
# Syslog facility to use if you're logging to syslog. Usually if you don't
# want to use "mail", you'll use local0..local7. Also other standard
# facilities are supported.
#syslog_facility = mail
##
## Logging verbosity and debugging.
##
# Log unsuccessful authentication attempts and the reasons why they failed.
auth_verbose = yes
# In case of password mismatches, log the attempted password. Valid values are
# no, plain and sha1. sha1 can be useful for detecting brute force password
# attempts vs. user simply trying the same password over and over again.
# You can also truncate the value to n chars by appending ":n" (e.g. sha1:6).
auth_verbose_passwords = sha1:6
# Even more verbose logging for debugging purposes. Shows for example SQL
# queries.
#auth_debug = no
# In case of password mismatches, log the passwords and used scheme so the
# problem can be debugged. Enabling this also enables auth_debug.
#auth_debug_passwords = no
# Enable mail process debugging. This can help you figure out why Dovecot
# isn't finding your mails.
#mail_debug = no
# Show protocol level SSL errors.
verbose_ssl = no
# mail_log plugin provides more event logging for mail processes.
plugin {
# Events to log. Also available: flag_change append
#mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
# Available fields: uid, box, msgid, from, subject, size, vsize, flags
# size and vsize are available only for expunge and copy events.
#mail_log_fields = uid box msgid size
}
##
## Log formatting.
##
# Prefix for each line written to log file. % codes are in strftime(3)
# format.
#log_timestamp = "%b %d %H:%M:%S "
# Space-separated list of elements we want to log. The elements which have
# a non-empty variable value are joined together to form a comma-separated
# string.
#login_log_format_elements = user=<%u> method=%m rip=%r lip=%l mpid=%e %c
# Login log format. %s contains login_log_format_elements string, %$ contains
# the data we want to log.
#login_log_format = %$: %s
# Log prefix for mail processes. See doc/wiki/Variables.txt for list of
# possible variables you can use.
#mail_log_prefix = "%s(%u): "
# Format to use for logging mail deliveries. You can use variables:
# %$ - Delivery status message (e.g. "saved to INBOX")
# %m - Message-ID
# %s - Subject
# %f - From address
# %p - Physical size
# %w - Virtual size
#deliver_log_format = msgid=%m: %$

374
dovecot/10-mail.conf Normal file
View file

@ -0,0 +1,374 @@
##
## Mailbox locations and namespaces
##
# Location for users' mailboxes. The default is empty, which means that Dovecot
# tries to find the mailboxes automatically. This won't work if the user
# doesn't yet have any mail, so you should explicitly tell Dovecot the full
# location.
#
# If you're using mbox, giving a path to the INBOX file (eg. /var/mail/%u)
# isn't enough. You'll also need to tell Dovecot where the other mailboxes are
# kept. This is called the "root mail directory", and it must be the first
# path given in the mail_location setting.
#
# There are a few special variables you can use, eg.:
#
# %u - username
# %n - user part in user@domain, same as %u if there's no domain
# %d - domain part in user@domain, empty if there's no domain
# %h - home directory
#
# See doc/wiki/Variables.txt for full list. Some examples:
#
# mail_location = maildir:~/Maildir
# mail_location = mbox:~/mail:INBOX=/var/mail/%u
# mail_location = mbox:/var/mail/%d/%1n/%n:INDEX=/var/indexes/%d/%1n/%n
#
# <doc/wiki/MailLocation.txt>
#
mail_location = maildir:/var/mail/%d/%n
# If you need to set multiple mailbox locations or want to change default
# namespace settings, you can do it by defining namespace sections.
#
# You can have private, shared and public namespaces. Private namespaces
# are for user's personal mails. Shared namespaces are for accessing other
# users' mailboxes that have been shared. Public namespaces are for shared
# mailboxes that are managed by sysadmin. If you create any shared or public
# namespaces you'll typically want to enable ACL plugin also, otherwise all
# users can access all the shared mailboxes, assuming they have permissions
# on filesystem level to do so.
namespace inbox {
# Namespace type: private, shared or public
#type = private
# Hierarchy separator to use. You should use the same separator for all
# namespaces or some clients get confused. '/' is usually a good one.
# The default however depends on the underlying mail storage format.
#separator =
# Prefix required to access this namespace. This needs to be different for
# all namespaces. For example "Public/".
#prefix =
# Physical location of the mailbox. This is in same format as
# mail_location, which is also the default for it.
#location =
# There can be only one INBOX, and this setting defines which namespace
# has it.
inbox = yes
# If namespace is hidden, it's not advertised to clients via NAMESPACE
# extension. You'll most likely also want to set list=no. This is mostly
# useful when converting from another server with different namespaces which
# you want to deprecate but still keep working. For example you can create
# hidden namespaces with prefixes "~/mail/", "~%u/mail/" and "mail/".
#hidden = no
# Show the mailboxes under this namespace with LIST command. This makes the
# namespace visible for clients that don't support NAMESPACE extension.
# "children" value lists child mailboxes, but hides the namespace prefix.
#list = yes
# Namespace handles its own subscriptions. If set to "no", the parent
# namespace handles them (empty prefix should always have this as "yes")
#subscriptions = yes
}
# Example shared namespace configuration
#namespace {
#type = shared
#separator = /
# Mailboxes are visible under "shared/user@domain/"
# %%n, %%d and %%u are expanded to the destination user.
#prefix = shared/%%u/
# Mail location for other users' mailboxes. Note that %variables and ~/
# expands to the logged in user's data. %%n, %%d, %%u and %%h expand to the
# destination user's data.
#location = maildir:%%h/Maildir:INDEX=~/Maildir/shared/%%u
# Use the default namespace for saving subscriptions.
#subscriptions = no
# List the shared/ namespace only if there are visible shared mailboxes.
#list = children
#}
# Should shared INBOX be visible as "shared/user" or "shared/user/INBOX"?
#mail_shared_explicit_inbox = no
# System user and group used to access mails. If you use multiple, userdb
# can override these by returning uid or gid fields. You can use either numbers
# or names. <doc/wiki/UserIds.txt>
#mail_uid =
#mail_gid =
# Group to enable temporarily for privileged operations. Currently this is
# used only with INBOX when either its initial creation or dotlocking fails.
# Typically this is set to "mail" to give access to /var/mail.
mail_privileged_group = docker
# Grant access to these supplementary groups for mail processes. Typically
# these are used to set up access to shared mailboxes. Note that it may be
# dangerous to set these if users can create symlinks (e.g. if "mail" group is
# set here, ln -s /var/mail ~/mail/var could allow a user to delete others'
# mailboxes, or ln -s /secret/shared/box ~/mail/mybox would allow reading it).
#mail_access_groups =
# Allow full filesystem access to clients. There's no access checks other than
# what the operating system does for the active UID/GID. It works with both
# maildir and mboxes, allowing you to prefix mailboxes names with eg. /path/
# or ~user/.
#mail_full_filesystem_access = no
# Dictionary for key=value mailbox attributes. Currently used by URLAUTH, but
# soon intended to be used by METADATA as well.
#mail_attribute_dict =
##
## Mail processes
##
# Don't use mmap() at all. This is required if you store indexes to shared
# filesystems (NFS or clustered filesystem).
#mmap_disable = no
# Rely on O_EXCL to work when creating dotlock files. NFS supports O_EXCL
# since version 3, so this should be safe to use nowadays by default.
#dotlock_use_excl = yes
# When to use fsync() or fdatasync() calls:
# optimized (default): Whenever necessary to avoid losing important data
# always: Useful with e.g. NFS when write()s are delayed
# never: Never use it (best performance, but crashes can lose data)
#mail_fsync = optimized
# Mail storage exists in NFS. Set this to yes to make Dovecot flush NFS caches
# whenever needed. If you're using only a single mail server this isn't needed.
#mail_nfs_storage = no
# Mail index files also exist in NFS. Setting this to yes requires
# mmap_disable=yes and fsync_disable=no.
#mail_nfs_index = no
# Locking method for index files. Alternatives are fcntl, flock and dotlock.
# Dotlocking uses some tricks which may create more disk I/O than other locking
# methods. NFS users: flock doesn't work, remember to change mmap_disable.
#lock_method = fcntl
# Directory in which LDA/LMTP temporarily stores incoming mails >128 kB.
#mail_temp_dir = /tmp
# Valid UID range for users, defaults to 500 and above. This is mostly
# to make sure that users can't log in as daemons or other system users.
# Note that denying root logins is hardcoded to dovecot binary and can't
# be done even if first_valid_uid is set to 0.
#first_valid_uid = 500
#last_valid_uid = 0
# Valid GID range for users, defaults to non-root/wheel. Users having
# non-valid GID as primary group ID aren't allowed to log in. If user
# belongs to supplementary groups with non-valid GIDs, those groups are
# not set.
#first_valid_gid = 1
#last_valid_gid = 0
# Maximum allowed length for mail keyword name. It's only forced when trying
# to create new keywords.
#mail_max_keyword_length = 50
# ':' separated list of directories under which chrooting is allowed for mail
# processes (ie. /var/mail will allow chrooting to /var/mail/foo/bar too).
# This setting doesn't affect login_chroot, mail_chroot or auth chroot
# settings. If this setting is empty, "/./" in home dirs are ignored.
# WARNING: Never add directories here which local users can modify, that
# may lead to root exploit. Usually this should be done only if you don't
# allow shell access for users. <doc/wiki/Chrooting.txt>
#valid_chroot_dirs =
# Default chroot directory for mail processes. This can be overridden for
# specific users in user database by giving /./ in user's home directory
# (eg. /home/./user chroots into /home). Note that usually there is no real
# need to do chrooting, Dovecot doesn't allow users to access files outside
# their mail directory anyway. If your home directories are prefixed with
# the chroot directory, append "/." to mail_chroot. <doc/wiki/Chrooting.txt>
#mail_chroot =
# UNIX socket path to master authentication server to find users.
# This is used by imap (for shared users) and lda.
#auth_socket_path = /var/run/dovecot/auth-userdb
# Directory where to look up mail plugins.
#mail_plugin_dir = /usr/lib/dovecot/modules
# Space separated list of plugins to load for all services. Plugins specific to
# IMAP, LDA, etc. are added to this list in their own .conf files.
#mail_plugins =
##
## Mailbox handling optimizations
##
# Mailbox list indexes can be used to optimize IMAP STATUS commands. They are
# also required for IMAP NOTIFY extension to be enabled.
#mailbox_list_index = no
# The minimum number of mails in a mailbox before updates are done to cache
# file. This allows optimizing Dovecot's behavior to do less disk writes at
# the cost of more disk reads.
#mail_cache_min_mail_count = 0
# When IDLE command is running, mailbox is checked once in a while to see if
# there are any new mails or other changes. This setting defines the minimum
# time to wait between those checks. Dovecot can also use dnotify, inotify and
# kqueue to find out immediately when changes occur.
#mailbox_idle_check_interval = 30 secs
# Save mails with CR+LF instead of plain LF. This makes sending those mails
# take less CPU, especially with sendfile() syscall with Linux and FreeBSD.
# But it also creates a bit more disk I/O which may just make it slower.
# Also note that if other software reads the mboxes/maildirs, they may handle
# the extra CRs wrong and cause problems.
#mail_save_crlf = no
# Max number of mails to keep open and prefetch to memory. This only works with
# some mailbox formats and/or operating systems.
#mail_prefetch_count = 0
# How often to scan for stale temporary files and delete them (0 = never).
# These should exist only after Dovecot dies in the middle of saving mails.
#mail_temp_scan_interval = 1w
##
## Maildir-specific settings
##
# By default LIST command returns all entries in maildir beginning with a dot.
# Enabling this option makes Dovecot return only entries which are directories.
# This is done by stat()ing each entry, so it causes more disk I/O.
# (For systems setting struct dirent->d_type, this check is free and it's
# done always regardless of this setting)
#maildir_stat_dirs = no
# When copying a message, do it with hard links whenever possible. This makes
# the performance much better, and it's unlikely to have any side effects.
#maildir_copy_with_hardlinks = yes
# Assume Dovecot is the only MUA accessing Maildir: Scan cur/ directory only
# when its mtime changes unexpectedly or when we can't find the mail otherwise.
#maildir_very_dirty_syncs = no
# If enabled, Dovecot doesn't use the S=<size> in the Maildir filenames for
# getting the mail's physical size, except when recalculating Maildir++ quota.
# This can be useful in systems where a lot of the Maildir filenames have a
# broken size. The performance hit for enabling this is very small.
#maildir_broken_filename_sizes = no
##
## mbox-specific settings
##
# Which locking methods to use for locking mbox. There are four available:
# dotlock: Create <mailbox>.lock file. This is the oldest and most NFS-safe
# solution. If you want to use /var/mail/ like directory, the users
# will need write access to that directory.
# dotlock_try: Same as dotlock, but if it fails because of permissions or
# because there isn't enough disk space, just skip it.
# fcntl : Use this if possible. Works with NFS too if lockd is used.
# flock : May not exist in all systems. Doesn't work with NFS.
# lockf : May not exist in all systems. Doesn't work with NFS.
#
# You can use multiple locking methods; if you do the order they're declared
# in is important to avoid deadlocks if other MTAs/MUAs are using multiple
# locking methods as well. Some operating systems don't allow using some of
# them simultaneously.
#
# The Debian value for mbox_write_locks differs from upstream Dovecot. It is
# changed to be compliant with Debian Policy (section 11.6) for NFS safety.
# Dovecot: mbox_write_locks = dotlock fcntl
# Debian: mbox_write_locks = fcntl dotlock
#
#mbox_read_locks = fcntl
#mbox_write_locks = fcntl dotlock
# Maximum time to wait for lock (all of them) before aborting.
#mbox_lock_timeout = 5 mins
# If dotlock exists but the mailbox isn't modified in any way, override the
# lock file after this much time.
#mbox_dotlock_change_timeout = 2 mins
# When mbox changes unexpectedly we have to fully read it to find out what
# changed. If the mbox is large this can take a long time. Since the change
# is usually just a newly appended mail, it'd be faster to simply read the
# new mails. If this setting is enabled, Dovecot does this but still safely
# fallbacks to re-reading the whole mbox file whenever something in mbox isn't
# how it's expected to be. The only real downside to this setting is that if
# some other MUA changes message flags, Dovecot doesn't notice it immediately.
# Note that a full sync is done with SELECT, EXAMINE, EXPUNGE and CHECK
# commands.
#mbox_dirty_syncs = yes
# Like mbox_dirty_syncs, but don't do full syncs even with SELECT, EXAMINE,
# EXPUNGE or CHECK commands. If this is set, mbox_dirty_syncs is ignored.
#mbox_very_dirty_syncs = no
# Delay writing mbox headers until doing a full write sync (EXPUNGE and CHECK
# commands and when closing the mailbox). This is especially useful for POP3
# where clients often delete all mails. The downside is that our changes
# aren't immediately visible to other MUAs.
#mbox_lazy_writes = yes
# If mbox size is smaller than this (e.g. 100k), don't write index files.
# If an index file already exists it's still read, just not updated.
#mbox_min_index_size = 0
# Mail header selection algorithm to use for MD5 POP3 UIDLs when
# pop3_uidl_format=%m. For backwards compatibility we use apop3d inspired
# algorithm, but it fails if the first Received: header isn't unique in all
# mails. An alternative algorithm is "all" that selects all headers.
#mbox_md5 = apop3d
##
## mdbox-specific settings
##
# Maximum dbox file size until it's rotated.
#mdbox_rotate_size = 2M
# Maximum dbox file age until it's rotated. Typically in days. Day begins
# from midnight, so 1d = today, 2d = yesterday, etc. 0 = check disabled.
#mdbox_rotate_interval = 0
# When creating new mdbox files, immediately preallocate their size to
# mdbox_rotate_size. This setting currently works only in Linux with some
# filesystems (ext4, xfs).
#mdbox_preallocate_space = no
##
## Mail attachments
##
# sdbox and mdbox support saving mail attachments to external files, which
# also allows single instance storage for them. Other backends don't support
# this for now.
# Directory root where to store mail attachments. Disabled, if empty.
#mail_attachment_dir =
# Attachments smaller than this aren't saved externally. It's also possible to
# write a plugin to disable saving specific attachments externally.
#mail_attachment_min_size = 128k
# Filesystem backend to use for saving attachments:
# posix : No SiS done by Dovecot (but this might help FS's own deduplication)
# sis posix : SiS with immediate byte-by-byte comparison during saving
# sis-queue posix : SiS with delayed comparison and deduplication
#mail_attachment_fs = sis posix
# Hash format to use in attachment filenames. You can add any text and
# variables: %{md4}, %{md5}, %{sha1}, %{sha256}, %{sha512}, %{size}.
# Variables can be truncated, e.g. %{sha256:80} returns only first 80 bits
#mail_attachment_hash = %{sha1}

127
dovecot/10-master.conf Normal file
View file

@ -0,0 +1,127 @@
#default_process_limit = 100
#default_client_limit = 1000
# Default VSZ (virtual memory size) limit for service processes. This is mainly
# intended to catch and kill processes that leak memory before they eat up
# everything.
#default_vsz_limit = 256M
# Login user is internally used by login processes. This is the most untrusted
# user in Dovecot system. It shouldn't have access to anything at all.
#default_login_user = dovenull
# Internal user is used by unprivileged processes. It should be separate from
# login user, so that login processes can't disturb other processes.
#default_internal_user = dovecot
service imap-login {
inet_listener imap {
#port = 143
}
inet_listener imaps {
#port = 993
#ssl = yes
}
# Number of connections to handle before starting a new process. Typically
# the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
# is faster. <doc/wiki/LoginProcess.txt>
#service_count = 1
# Number of processes to always keep waiting for more connections.
#process_min_avail = 0
# If you set service_count=0, you probably need to grow this.
#vsz_limit = $default_vsz_limit
}
service pop3-login {
inet_listener pop3 {
#port = 110
}
inet_listener pop3s {
#port = 995
#ssl = yes
}
}
service lmtp {
unix_listener lmtp {
#mode = 0666
}
# Create inet listener only if you can't use the above UNIX socket
#inet_listener lmtp {
# Avoid making LMTP visible for the entire internet
#address =
#port =
#}
}
service imap {
# Most of the memory goes to mmap()ing files. You may need to increase this
# limit if you have huge mailboxes.
#vsz_limit = $default_vsz_limit
# Max. number of IMAP processes (connections)
#process_limit = 1024
}
service pop3 {
# Max. number of POP3 processes (connections)
#process_limit = 1024
}
service auth {
# auth_socket_path points to this userdb socket by default. It's typically
# used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
# full permissions to this socket are able to get a list of all usernames and
# get the results of everyone's userdb lookups.
#
# The default 0666 mode allows anyone to connect to the socket, but the
# userdb lookups will succeed only if the userdb returns an "uid" field that
# matches the caller process's UID. Also if caller's uid or gid matches the
# socket's uid or gid the lookup succeeds. Anything else causes a failure.
#
# To give the caller full permissions to lookup all users, set the mode to
# something else than 0666 and Dovecot lets the kernel enforce the
# permissions (e.g. 0777 allows everyone full permissions).
unix_listener auth-userdb {
mode = 0666
user = docker
group = docker
}
unix_listener auth-master {
mode = 0600
user = docker
group = docker
}
# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = docker
group = docker
}
# Auth process is run as this user.
#user = $default_internal_user
}
service auth-worker {
# Auth worker process is run as root by default, so that it can access
# /etc/shadow. If this isn't necessary, the user should be changed to
# $default_internal_user.
#user = root
}
service dict {
# If dict proxy is used, mail processes should have access to its socket.
# For example: mode=0660, group=vmail and global mail_access_groups=vmail
unix_listener dict {
#mode = 0600
#user =
#group =
}
}

58
dovecot/10-ssl.conf Normal file
View file

@ -0,0 +1,58 @@
##
## SSL settings
##
# SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt>
#ssl = yes
# PEM encoded X.509 SSL/TLS certificate and private key. They're opened before
# dropping root privileges, so keep the key file unreadable by anyone but
# root. Included doc/mkcert.sh can be used to easily generate self-signed
# certificate, just make sure to update the domains in dovecot-openssl.cnf
ssl_cert = </etc/dovecot/dovecot.pem
ssl_key = </etc/dovecot/private/dovecot.pem
# If key file is password protected, give the password here. Alternatively
# give it when starting dovecot with -p parameter. Since this file is often
# world-readable, you may want to place this setting instead to a different
# root owned 0600 file by using ssl_key_password = <path.
#ssl_key_password =
# PEM encoded trusted certificate authority. Set this only if you intend to use
# ssl_verify_client_cert=yes. The file should contain the CA certificate(s)
# followed by the matching CRL(s). (e.g. ssl_ca = </etc/ssl/certs/ca.pem)
#ssl_ca =
# Require that CRL check succeeds for client certificates.
#ssl_require_crl = yes
# Directory and/or file for trusted SSL CA certificates. These are used only
# when Dovecot needs to act as an SSL client (e.g. imapc backend). The
# directory is usually /etc/ssl/certs in Debian-based systems and the file is
# /etc/pki/tls/cert.pem in RedHat-based systems.
#ssl_client_ca_dir =
#ssl_client_ca_file =
# Request client to send a certificate. If you also want to require it, set
# auth_ssl_require_client_cert=yes in auth section.
#ssl_verify_client_cert = no
# Which field from certificate to use for username. commonName and
# x500UniqueIdentifier are the usual choices. You'll also need to set
# auth_ssl_username_from_cert=yes.
#ssl_cert_username_field = commonName
# DH parameters length to use.
#ssl_dh_parameters_length = 1024
# SSL protocols to use
#ssl_protocols = !SSLv2
# SSL ciphers to use
#ssl_cipher_list = ALL:!LOW:!SSLv2:!EXP:!aNULL
# Prefer the server's order of ciphers over client's.
#ssl_prefer_server_ciphers = no
# SSL crypto device to use, for valid values run "openssl engine"
#ssl_crypto_device =

50
dovecot/15-mailboxes.conf Normal file
View file

@ -0,0 +1,50 @@
##
## Mailbox definitions
##
# NOTE: Assumes "namespace inbox" has been defined in 10-mail.conf.
namespace inbox {
#mailbox name {
# auto=create will automatically create this mailbox.
# auto=subscribe will both create and subscribe to the mailbox.
#auto = no
# Space separated list of IMAP SPECIAL-USE attributes as specified by
# RFC 6154: \All \Archive \Drafts \Flagged \Junk \Sent \Trash
#special_use =
#}
# These mailboxes are widely used and could perhaps be created automatically:
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
special_use = \Junk
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
# For \Sent mailboxes there are two widely used names. We'll mark both of
# them as \Sent. User typically deletes one of them if duplicates are created.
mailbox Sent {
auto = subscribe
special_use = \Sent
}
# mailbox "Sent Messages" {
# special_use = \Sent
# }
# If you have a virtual "All messages" mailbox:
#mailbox virtual/All {
# special_use = \All
#}
# If you have a virtual "Flagged" mailbox:
#mailbox virtual/Flagged {
# special_use = \Flagged
#}
}

View file

@ -0,0 +1,21 @@
# Authentication for passwd-file users. Included from 10-auth.conf.
#
# passwd-like file with specified location.
# <doc/wiki/AuthDatabase.PasswdFile.txt>
passdb {
driver = passwd-file
args = scheme=CRYPT username_format=%u /etc/dovecot/userdb
}
userdb {
driver = passwd-file
args = username_format=%u /etc/dovecot/userdb
# Default fields that can be overridden by passwd-file
#default_fields = quota_rule=*:storage=1G
default_fields = uid=docker gid=docker home=/var/mail/%d/%u
# Override fields from passwd-file
#override_fields = home=/home/virtual/%u
}

102
dovecot/dovecot.conf Normal file
View file

@ -0,0 +1,102 @@
## Dovecot configuration file
# If you're in a hurry, see http://wiki2.dovecot.org/QuickConfiguration
# "doveconf -n" command gives a clean output of the changed settings. Use it
# instead of copy&pasting files when posting to the Dovecot mailing list.
# '#' character and everything after it is treated as comments. Extra spaces
# and tabs are ignored. If you want to use either of these explicitly, put the
# value inside quotes, eg.: key = "# char and trailing whitespace "
# Most (but not all) settings can be overridden by different protocols and/or
# source/destination IPs by placing the settings inside sections, for example:
# protocol imap { }, local 127.0.0.1 { }, remote 10.0.0.0/8 { }
# Default values are shown for each setting, it's not required to uncomment
# those. These are exceptions to this though: No sections (e.g. namespace {})
# or plugin settings are added by default, they're listed only as examples.
# Paths are also just examples with the real defaults being based on configure
# options. The paths listed here are for configure --prefix=/usr
# --sysconfdir=/etc --localstatedir=/var
# Enable installed protocols
!include_try /etc/dovecot/protocols.d/*.protocol
# A comma separated list of IPs or hosts where to listen in for connections.
# "*" listens in all IPv4 interfaces, "::" listens in all IPv6 interfaces.
# If you want to specify non-default ports or anything more complex,
# edit conf.d/master.conf.
#listen = *, ::
# Base directory where to store runtime data.
#base_dir = /var/run/dovecot/
# Name of this instance. In multi-instance setup doveadm and other commands
# can use -i <instance_name> to select which instance is used (an alternative
# to -c <config_path>). The instance name is also added to Dovecot processes
# in ps output.
#instance_name = dovecot
# Greeting message for clients.
#login_greeting = Dovecot ready.
# Space separated list of trusted network ranges. Connections from these
# IPs are allowed to override their IP addresses and ports (for logging and
# for authentication checks). disable_plaintext_auth is also ignored for
# these networks. Typically you'd specify your IMAP proxy servers here.
#login_trusted_networks =
# Space separated list of login access check sockets (e.g. tcpwrap)
#login_access_sockets =
# With proxy_maybe=yes if proxy destination matches any of these IPs, don't do
# proxying. This isn't necessary normally, but may be useful if the destination
# IP is e.g. a load balancer's IP.
#auth_proxy_self =
# Show more verbose process titles (in ps). Currently shows user name and
# IP address. Useful for seeing who are actually using the IMAP processes
# (eg. shared mailboxes or if same uid is used for multiple accounts).
#verbose_proctitle = no
# Should all processes be killed when Dovecot master process shuts down.
# Setting this to "no" means that Dovecot can be upgraded without
# forcing existing client connections to close (although that could also be
# a problem if the upgrade is e.g. because of a security fix).
#shutdown_clients = yes
# If non-zero, run mail commands via this many connections to doveadm server,
# instead of running them directly in the same process.
#doveadm_worker_count = 0
# UNIX socket or host:port used for connecting to doveadm server
#doveadm_socket_path = doveadm-server
# Space separated list of environment variables that are preserved on Dovecot
# startup and passed down to all of its child processes. You can also give
# key=value pairs to always set specific settings.
#import_environment = TZ
##
## Dictionary server settings
##
# Dictionary can be used to store key=value lists. This is used by several
# plugins. The dictionary can be accessed either directly or though a
# dictionary server. The following dict block maps dictionary names to URIs
# when the server is used. These can then be referenced using URIs in format
# "proxy::<name>".
dict {
#quota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
#expire = sqlite:/etc/dovecot/dovecot-dict-sql.conf.ext
}
# Most of the actual configuration gets included below. The filenames are
# first sorted by their ASCII value and parsed in that order. The 00-prefixes
# in filenames are intended to make it easier to understand the ordering.
!include conf.d/*.conf
# A config file can also tried to be included without giving an error if
# it's not found:
#!include_try local.conf

2
postfix/TrustedHosts Normal file
View file

@ -0,0 +1,2 @@
127.0.0.1
localhost

1
postfix/accounts.cf Normal file
View file

@ -0,0 +1 @@
user@domain.tld|mypassword

12
postfix/default-opendkim Normal file
View file

@ -0,0 +1,12 @@
# Command-line options specified here will override the contents of
# /etc/opendkim.conf. See opendkim(8) for a complete list of options.
#DAEMON_OPTS=""
#
# Uncomment to specify an alternate socket
# Note that setting this will override any Socket value in opendkim.conf
#SOCKET="local:/var/run/opendkim/opendkim.sock" # default
#SOCKET="inet:54321" # listen on all interfaces on port 54321
#SOCKET="inet:12345@localhost" # listen on loopback on port 12345
#SOCKET="inet:12345@192.0.2.1" # listen on 192.0.2.1 on port 12345
SOCKET="inet:12301@localhost"

11
postfix/default-opendmarc Normal file
View file

@ -0,0 +1,11 @@
# Command-line options specified here will override the contents of
# /etc/opendmarc.conf. See opendmarc(8) for a complete list of options.
#DAEMON_OPTS=""
#
# Uncomment to specify an alternate socket
# Note that setting this will override any Socket value in opendkim.conf
#SOCKET="local:/var/run/opendmarc/opendmarc.sock" # default
#SOCKET="inet:54321" # listen on all interfaces on port 54321
#SOCKET="inet:12345@localhost" # listen on loopback on port 12345
#SOCKET="inet:12345@192.0.2.1" # listen on 192.0.2.1 on port 12345
SOCKET="inet:54321@localhost"

64
postfix/main.cf Normal file
View file

@ -0,0 +1,64 @@
# See /usr/share/postfix/main.cf.dist for a commented, more complete version
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
readme_directory = no
# Basic configuration
# myhostname =
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination =
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 172.17.0.0/16
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
#smtpd_tls_CAfile=
#smtp_tls_CAfile=
smtpd_tls_security_level = may
smtpd_use_tls=yes
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
smtpd_sender_restrictions = permit_mynetworks
smtp_tls_security_level = may
smtp_tls_loglevel = 1
tls_ssl_options = NO_COMPRESSION
tls_high_cipherlist=EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!ECDSA:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA
smtpd_tls_protocols=!SSLv2,!SSLv3
smtp_tls_protocols=!SSLv2,!SSLv3
smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3
smtpd_tls_exclude_ciphers = aNULL, LOW, EXP, MEDIUM, ADH, AECDH, MD5, DSS, ECDSA, CAMELLIA128, 3DES, CAMELLIA256, RSA+AES, eNULL
# SASL
smtpd_sasl_auth_enable = yes
smtpd_sasl_path = private/auth
smtpd_sasl_type = dovecot
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname
broken_sasl_auth_clients = yes
# Mail directory
virtual_mailbox_base = /var/mail
virtual_mailbox_domains = /etc/postfix/vhost
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_alias_maps = hash:/etc/postfix/virtual
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
# Additional option for filtering
content_filter = smtp-amavis:[127.0.0.1]:10024
# Milters used by DKIM
milter_protocol = 2
milter_default_action = accept
smtpd_milters = inet:localhost:12301,inet:localhost:54321
non_smtpd_milters = inet:localhost:12301,inet:localhost:54321

97
postfix/master.cf Normal file
View file

@ -0,0 +1,97 @@
#
# Postfix master process configuration file. For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
smtp inet n - n - - smtpd
submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_reject_unlisted_recipient=no
-o smtpd_sasl_authenticated_header=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
pickup fifo n - - 60 1 pickup
-o content_filter=
-o receive_override_options=no_header_body_checks
cleanup unix n - - - 0 cleanup
qmgr unix n - n 300 1 qmgr
tlsmgr unix - - - 1000? 1 tlsmgr
rewrite unix - - - - - trivial-rewrite
bounce unix - - - - 0 bounce
defer unix - - - - 0 bounce
trace unix - - - - 0 bounce
verify unix - - - - 1 verify
flush unix n - - 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - - - - smtp
relay unix - - - - - smtp
showq unix n - - - - showq
error unix - - - - - error
retry unix - - - - - error
discard unix - - - - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - - - - lmtp
anvil unix - - - - 1 anvil
scache unix - - - - 1 scache
maildrop unix - n n - - pipe
flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
uucp unix - n n - - pipe
flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
ifmail unix - n n - - pipe
flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp unix - n n - - pipe
flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix - n n - 2 pipe
flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman unix - n n - - pipe
flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
${nexthop} ${user}
#
# Amavis configuraiton
#
smtp-amavis unix - - - - 2 smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20
-o smtp_tls_security_level=none
127.0.0.1:10025 inet n - n - - smtpd
-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
-o smtp_tls_security_level=none

22
postfix/opendkim.conf Normal file
View file

@ -0,0 +1,22 @@
AutoRestart Yes
AutoRestartRate 10/1h
UMask 002
Syslog yes
SyslogSuccess Yes
LogWhy Yes
RemoveOldSignatures Yes
Canonicalization relaxed/simple
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
InternalHosts refile:/etc/opendkim/TrustedHosts
KeyTable refile:/etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
Mode sv
PidFile /var/run/opendkim/opendkim.pid
SignatureAlgorithm rsa-sha256
UserID opendkim:opendkim
Socket inet:12301@localhost

8
postfix/opendmarc.conf Normal file
View file

@ -0,0 +1,8 @@
PidFile /var/run/opendmarc.pid
RejectFailures false
Syslog true
UMask 0002
UserID opendmarc:opendmarc
IgnoreHosts /etc/opendmarc/ignore.hosts
HistoryFile /var/run/opendmarc/opendmarc.dat

7
postfix/virtual Normal file
View file

@ -0,0 +1,7 @@
#
# ALIAS => from alias@domain.tld (alias) to user@domain.tld (real account), space separated
# alias@domain.tld user@domain.tld
#
# FORWARD => from redirect@domain.tld to a list of internal/external email addresses, space separated
# redirect@domain.tld otheruser@domain.tld otheruser@otherdomain.tld
#

1
spamassassin/rules.cf Normal file
View file

@ -0,0 +1 @@
# Place you custom Spamassasin rules here

335
start-mailserver.sh Normal file
View file

@ -0,0 +1,335 @@
#!/bin/bash
die () {
echo >&2 "$@"
exit 1
}
if [ -f /tmp/postfix/accounts.cf ]; then
echo "Regenerating postfix 'vmailbox' and 'virtual' for given users"
echo "# WARNING: this file is auto-generated. Modify accounts.cf in postfix directory on host" > /etc/postfix/vmailbox
# Checking that /tmp/postfix/accounts.cf ends with a newline
sed -i -e '$a\' /tmp/postfix/accounts.cf
# Configuring Dovecot
echo -n > /etc/dovecot/userdb
chown dovecot:dovecot /etc/dovecot/userdb
chmod 640 /etc/dovecot/userdb
cp -a /usr/share/dovecot/protocols.d /etc/dovecot/
# Disable pop3 (it will be eventually enabled later in the script, if requested)
mv /etc/dovecot/protocols.d/pop3d.protocol /etc/dovecot/protocols.d/pop3d.protocol.disab
sed -i -e 's/#ssl = yes/ssl = yes/g' /etc/dovecot/conf.d/10-master.conf
sed -i -e 's/#port = 993/port = 993/g' /etc/dovecot/conf.d/10-master.conf
sed -i -e 's/#port = 995/port = 995/g' /etc/dovecot/conf.d/10-master.conf
sed -i -e 's/#ssl = yes/ssl = required/g' /etc/dovecot/conf.d/10-ssl.conf
# Creating users
# pass is encrypted
while IFS=$'|' read login pass
do
# Setting variables for better readability
user=$(echo ${login} | cut -d @ -f1)
domain=$(echo ${login} | cut -d @ -f2)
# Let's go!
echo "user '${user}' for domain '${domain}' with password '********'"
echo "${login} ${domain}/${user}/" >> /etc/postfix/vmailbox
# user database for dovecot has the following format:
# user:password:uid:gid:(gecos):home:(shell):extra_fields
# Example : ${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::userdb_mail=maildir:/var/mail/${domain}/${user}
echo "${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::" >> /etc/dovecot/userdb
mkdir -p /var/mail/${domain}
if [ ! -d "/var/mail/${domain}/${user}" ]; then
maildirmake.dovecot "/var/mail/${domain}/${user}"
maildirmake.dovecot "/var/mail/${domain}/${user}/.Sent"
maildirmake.dovecot "/var/mail/${domain}/${user}/.Trash"
maildirmake.dovecot "/var/mail/${domain}/${user}/.Drafts"
echo -e "INBOX\nSent\nTrash\nDrafts" >> "/var/mail/${domain}/${user}/subscriptions"
touch "/var/mail/${domain}/${user}/.Sent/maildirfolder"
fi
echo ${domain} >> /tmp/vhost.tmp
done < /tmp/postfix/accounts.cf
else
echo "==> Warning: '/tmp/postfix/accounts.cf' is not provided. No mail account created."
fi
if [ -f /tmp/postfix/virtual ]; then
# Copying virtual file
cp /tmp/postfix/virtual /etc/postfix/virtual
while IFS=$' ' read from to
do
# Setting variables for better readability
uname=$(echo ${from} | cut -d @ -f1)
domain=$(echo ${from} | cut -d @ -f2)
# if they are equal it means the line looks like: "user1 other@domain.tld"
test "$uname" != "$domain" && echo ${domain} >> /tmp/vhost.tmp
done < /tmp/postfix/virtual
else
echo "==> Warning: '/tmp/postfix/virtual' is not provided. No mail alias created."
fi
if [ -f /tmp/vhost.tmp ]; then
cat /tmp/vhost.tmp | sort | uniq > /etc/postfix/vhost && rm /tmp/vhost.tmp
fi
echo "Postfix configurations"
touch /etc/postfix/vmailbox && postmap /etc/postfix/vmailbox
touch /etc/postfix/virtual && postmap /etc/postfix/virtual
# DKIM
# Check if keys are already available
if [ -e "/tmp/postfix/opendkim/KeyTable" ]; then
mkdir -p /etc/opendkim
cp -a /tmp/postfix/opendkim/* /etc/opendkim/
echo "DKIM keys added for : `ls -C /etc/opendkim/keys/`"
else
grep -vE '^(\s*$|#)' /etc/postfix/vhost | while read domainname; do
mkdir -p /etc/opendkim/keys/$domainname
if [ ! -f "/etc/opendkim/keys/$domainname/mail.private" ]; then
echo "Creating DKIM private key /etc/opendkim/keys/$domainname/mail.private"
pushd /etc/opendkim/keys/$domainname
opendkim-genkey --subdomains --domain=$domainname --selector=mail
popd
echo ""
echo "DKIM PUBLIC KEY ################################################################"
cat /etc/opendkim/keys/$domainname/mail.txt
echo "################################################################################"
fi
# Write to KeyTable if necessary
keytableentry="mail._domainkey.$domainname $domainname:mail:/etc/opendkim/keys/$domainname/mail.private"
if [ ! -f "/etc/opendkim/KeyTable" ]; then
echo "Creating DKIM KeyTable"
echo "mail._domainkey.$domainname $domainname:mail:/etc/opendkim/keys/$domainname/mail.private" > /etc/opendkim/KeyTable
else
if ! grep -q "$keytableentry" "/etc/opendkim/KeyTable" ; then
echo $keytableentry >> /etc/opendkim/KeyTable
fi
fi
# Write to SigningTable if necessary
signingtableentry="*@$domainname mail._domainkey.$domainname"
if [ ! -f "/etc/opendkim/SigningTable" ]; then
echo "Creating DKIM SigningTable"
echo "*@$domainname mail._domainkey.$domainname" > /etc/opendkim/SigningTable
else
if ! grep -q "$signingtableentry" "/etc/opendkim/SigningTable" ; then
echo $signingtableentry >> /etc/opendkim/SigningTable
fi
fi
done
fi
echo "Changing permissions on /etc/opendkim"
# chown entire directory
chown -R opendkim:opendkim /etc/opendkim/
# And make sure permissions are right
chmod -R 0700 /etc/opendkim/keys/
# DMARC
# if there is no AuthservID create it
if [ `cat /etc/opendmarc.conf | grep -w AuthservID | wc -l` -eq 0 ]; then
echo "AuthservID $(hostname)" >> /etc/opendmarc.conf
fi
if [ `cat /etc/opendmarc.conf | grep -w TrustedAuthservIDs | wc -l` -eq 0 ]; then
echo "TrustedAuthservIDs $(hostname)" >> /etc/opendmarc.conf
fi
if [ ! -f "/etc/opendmarc/ignore.hosts" ]; then
mkdir -p /etc/opendmarc/
echo "localhost" >> /etc/opendmarc/ignore.hosts
fi
# SSL Configuration
case $DMS_SSL in
"letsencrypt" )
# letsencrypt folders and files mounted in /etc/letsencrypt
if [ -e "/etc/letsencrypt/live/$(hostname)/cert.pem" ] \
&& [ -e "/etc/letsencrypt/live/$(hostname)/chain.pem" ] \
&& [ -e "/etc/letsencrypt/live/$(hostname)/privkey.pem" ]; then
echo "Adding $(hostname) SSL certificate"
# create combined.pem from (cert|chain|privkey).pem with eol after each .pem
sed -e '$a\' -s /etc/letsencrypt/live/$(hostname)/{cert,chain,privkey}.pem > /etc/letsencrypt/live/$(hostname)/combined.pem
# Postfix configuration
sed -i -r 's/smtpd_tls_cert_file=\/etc\/ssl\/certs\/ssl-cert-snakeoil.pem/smtpd_tls_cert_file=\/etc\/letsencrypt\/live\/'$(hostname)'\/fullchain.pem/g' /etc/postfix/main.cf
sed -i -r 's/smtpd_tls_key_file=\/etc\/ssl\/private\/ssl-cert-snakeoil.key/smtpd_tls_key_file=\/etc\/letsencrypt\/live\/'$(hostname)'\/privkey.pem/g' /etc/postfix/main.cf
# Dovecot configuration
sed -i -e 's/ssl_cert = <\/etc\/dovecot\/dovecot\.pem/ssl_cert = <\/etc\/letsencrypt\/live\/'$(hostname)'\/fullchain\.pem/g' /etc/dovecot/conf.d/10-ssl.conf
sed -i -e 's/ssl_key = <\/etc\/dovecot\/private\/dovecot\.pem/ssl_key = <\/etc\/letsencrypt\/live\/'$(hostname)'\/privkey\.pem/g' /etc/dovecot/conf.d/10-ssl.conf
echo "SSL configured with letsencrypt certificates"
fi
;;
"custom" )
# Adding CA signed SSL certificate if provided in 'postfix/ssl' folder
if [ -e "/tmp/postfix/ssl/$(hostname)-full.pem" ]; then
echo "Adding $(hostname) SSL certificate"
mkdir -p /etc/postfix/ssl
cp "/tmp/postfix/ssl/$(hostname)-full.pem" /etc/postfix/ssl
# Postfix configuration
sed -i -r 's/smtpd_tls_cert_file=\/etc\/ssl\/certs\/ssl-cert-snakeoil.pem/smtpd_tls_cert_file=\/etc\/postfix\/ssl\/'$(hostname)'-full.pem/g' /etc/postfix/main.cf
sed -i -r 's/smtpd_tls_key_file=\/etc\/ssl\/private\/ssl-cert-snakeoil.key/smtpd_tls_key_file=\/etc\/postfix\/ssl\/'$(hostname)'-full.pem/g' /etc/postfix/main.cf
# Dovecot configuration
sed -i -e 's/ssl_cert = <\/etc\/dovecot\/dovecot\.pem/ssl_cert = <\/etc\/postfix\/ssl\/'$(hostname)'-full\.pem/g' /etc/dovecot/conf.d/10-ssl.conf
sed -i -e 's/ssl_key = <\/etc\/dovecot\/private\/dovecot\.pem/ssl_key = <\/etc\/postfix\/ssl\/'$(hostname)'-full\.pem/g' /etc/dovecot/conf.d/10-ssl.conf
echo "SSL configured with CA signed/custom certificates"
fi
;;
"self-signed" )
# Adding self-signed SSL certificate if provided in 'postfix/ssl' folder
if [ -e "/tmp/postfix/ssl/$(hostname)-cert.pem" ] \
&& [ -e "/tmp/postfix/ssl/$(hostname)-key.pem" ] \
&& [ -e "/tmp/postfix/ssl/$(hostname)-combined.pem" ] \
&& [ -e "/tmp/postfix/ssl/demoCA/cacert.pem" ]; then
echo "Adding $(hostname) SSL certificate"
mkdir -p /etc/postfix/ssl
cp "/tmp/postfix/ssl/$(hostname)-cert.pem" /etc/postfix/ssl
cp "/tmp/postfix/ssl/$(hostname)-key.pem" /etc/postfix/ssl
# Force permission on key file
chmod 600 /etc/postfix/ssl/$(hostname)-key.pem
cp "/tmp/postfix/ssl/$(hostname)-combined.pem" /etc/postfix/ssl
cp /tmp/postfix/ssl/demoCA/cacert.pem /etc/postfix/ssl
# Postfix configuration
sed -i -r 's/smtpd_tls_cert_file=\/etc\/ssl\/certs\/ssl-cert-snakeoil.pem/smtpd_tls_cert_file=\/etc\/postfix\/ssl\/'$(hostname)'-cert.pem/g' /etc/postfix/main.cf
sed -i -r 's/smtpd_tls_key_file=\/etc\/ssl\/private\/ssl-cert-snakeoil.key/smtpd_tls_key_file=\/etc\/postfix\/ssl\/'$(hostname)'-key.pem/g' /etc/postfix/main.cf
sed -i -r 's/#smtpd_tls_CAfile=/smtpd_tls_CAfile=\/etc\/postfix\/ssl\/cacert.pem/g' /etc/postfix/main.cf
sed -i -r 's/#smtp_tls_CAfile=/smtp_tls_CAfile=\/etc\/postfix\/ssl\/cacert.pem/g' /etc/postfix/main.cf
ln -s /etc/postfix/ssl/cacert.pem "/etc/ssl/certs/cacert-$(hostname).pem"
# Dovecot configuration
sed -i -e 's/ssl_cert = <\/etc\/dovecot\/dovecot\.pem/ssl_cert = <\/etc\/postfix\/ssl\/'$(hostname)'-combined\.pem/g' /etc/dovecot/conf.d/10-ssl.conf
sed -i -e 's/ssl_key = <\/etc\/dovecot\/private\/dovecot\.pem/ssl_key = <\/etc\/postfix\/ssl\/'$(hostname)'-key\.pem/g' /etc/dovecot/conf.d/10-ssl.conf
echo "SSL configured with self-signed/custom certificates"
fi
;;
esac
if [ -f /tmp/postfix/main.cf ]; then
while read line; do
postconf -e "$line"
done < /tmp/postfix/main.cf
echo "Loaded '/tmp/postfix/main.cf'"
else
echo "'/tmp/postfix/main.cf' not provided. No extra postfix settings loaded."
fi
if [ ! -z "$SASL_PASSWD" ]; then
echo "$SASL_PASSWD" > /etc/postfix/sasl_passwd
postmap hash:/etc/postfix/sasl_passwd
rm /etc/postfix/sasl_passwd
chown root:root /etc/postfix/sasl_passwd.db
chmod 0600 /etc/postfix/sasl_passwd.db
echo "Loaded SASL_PASSWORD"
else
echo "==> Warning: 'SASL_PASSWORD' is not provided. /etc/postfix/sasl_passwd not created."
fi
echo "Fixing permissions"
chown -R 5000:5000 /var/mail
echo "Creating /etc/mailname"
echo $(hostname -d) > /etc/mailname
echo "Configuring Spamassassin"
SA_TAG=${SA_TAG:="2.0"} && sed -i -r 's/^\$sa_tag_level_deflt (.*);/\$sa_tag_level_deflt = '$SA_TAG';/g' /etc/amavis/conf.d/20-debian_defaults
SA_TAG2=${SA_TAG2:="6.31"} && sed -i -r 's/^\$sa_tag2_level_deflt (.*);/\$sa_tag2_level_deflt = '$SA_TAG2';/g' /etc/amavis/conf.d/20-debian_defaults
SA_KILL=${SA_KILL:="6.31"} && sed -i -r 's/^\$sa_kill_level_deflt (.*);/\$sa_kill_level_deflt = '$SA_KILL';/g' /etc/amavis/conf.d/20-debian_defaults
test -e /tmp/spamassassin/rules.cf && cp /tmp/spamassassin/rules.cf /etc/spamassassin/
echo "Configuring fail2ban"
# enable filters
awk 'BEGIN{unit=0}{if ($1=="[postfix]" || $1=="[dovecot]" || $1=="[sasl]") {unit=1;}
if ($1=="enabled" && unit==1) $3="true";
else if ($1=="logpath" && unit==1) $3="/var/log/mail/mail.log";
print;
if (unit==1 && $1~/\[/ && $1!~/postfix|dovecot|sasl/) unit=0;
}' /etc/fail2ban/jail.conf > /tmp/jail.conf.new && mv /tmp/jail.conf.new /etc/fail2ban/jail.conf && rm -f /tmp/jail.conf.new
cat > /etc/fail2ban/filter.d/dovecot.conf << _EOF_
# Fail2Ban filter Dovecot authentication and pop3/imap server
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = (auth|dovecot(-auth)?|auth-worker)
failregex = ^%(__prefix_line)s(pam_unix(\(dovecot:auth\))?:)?\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=<HOST>(\s+user=\S*)?\s*$
^%(__prefix_line)s(pop3|imap)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((no auth attempts|auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>, lip=(\d{1,3}\.){3}\d{1,3}(, session=<\w+>)?(, TLS( handshaking)?(: Disconnected)?)?\s*$
^%(__prefix_line)s(Info|dovecot: auth\(default\)): pam\(\S+,<HOST>\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$
^\s.*passwd-file\(\S*,<HOST>\): unknown user.*$
(?: pop3-login|imap-login): .*(?:Authentication failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed).*rip=(?P<host>\S*),.*
## ^%(__prefix_line)spasswd-file\(\S*,<HOST>\): unknown user.*$
ignoreregex =
_EOF_
# increase ban time and find time to 3h
sed -i "/^bantime *=/c\bantime = 10800" /etc/fail2ban/jail.conf
sed -i "/^findtime *=/c\findtime = 10800" /etc/fail2ban/jail.conf
# avoid warning on startup
echo "ignoreregex =" >> /etc/fail2ban/filter.d/postfix-sasl.conf
# continue to write the log information in the newly created file after rotating the old log file
sed -i -r "/^#?compress/c\compress\ncopytruncate" /etc/logrotate.conf
# Setup logging
mkdir -p /var/log/mail && chown syslog:root /var/log/mail
touch /var/log/mail/clamav.log && chown -R clamav:root /var/log/mail/clamav.log
touch /var/log/mail/freshclam.log && chown -R clamav:root /var/log/mail/freshclam.log
sed -i -r 's|/var/log/mail|/var/log/mail/mail|g' /etc/rsyslog.d/50-default.conf
sed -i -r 's|LogFile /var/log/clamav/|LogFile /var/log/mail/|g' /etc/clamav/clamd.conf
sed -i -r 's|UpdateLogFile /var/log/clamav/|UpdateLogFile /var/log/mail/|g' /etc/clamav/freshclam.conf
sed -i -r 's|/var/log/clamav|/var/log/mail|g' /etc/logrotate.d/clamav-daemon
sed -i -r 's|/var/log/clamav|/var/log/mail|g' /etc/logrotate.d/clamav-freshclam
sed -i -r 's|/var/log/mail|/var/log/mail/mail|g' /etc/logrotate.d/rsyslog
echo "Starting daemons"
cron
/etc/init.d/rsyslog start
##/etc/init.d/saslauthd start
if [ "$SMTP_ONLY" != 1 ]; then
# Here we are starting sasl and imap, not pop3 because it's disabled by default
echo " * Starting dovecot services"
/usr/sbin/dovecot -F -c /etc/dovecot/dovecot.conf &
fi
if [ "$ENABLE_POP3" = 1 -a "$SMTP_ONLY" != 1 ]; then
echo "Starting POP3 services"
mv /etc/dovecot/protocols.d/pop3d.protocol.disab /etc/dovecot/protocols.d/pop3d.protocol
/usr/sbin/dovecot reload
fi
/etc/init.d/spamassassin start
/etc/init.d/clamav-daemon start
/etc/init.d/amavis start
/etc/init.d/opendkim start
/etc/init.d/opendmarc start
/etc/init.d/postfix start
if [ "$ENABLE_FAIL2BAN" = 1 ]; then
echo "Starting fail2ban service"
/etc/init.d/fail2ban start
fi
echo "Listing users"
/usr/sbin/dovecot user '*'
echo "Starting..."
tail -f /var/log/mail/mail.log

2
test/accounts.cf Normal file
View file

@ -0,0 +1,2 @@
user1@localhost.localdomain|mypassword
user2@otherdomain.tld|mypassword

4
test/auth/imap-auth.txt Normal file
View file

@ -0,0 +1,4 @@
a1 LOGIN user1@localhost.localdomain mypassword
a3 EXAMINE INBOX
a4 FETCH 1 BODY[]
a5 LOGOUT

4
test/auth/pop3-auth.txt Normal file
View file

@ -0,0 +1,4 @@
USER user1@localhost.localdomain
PASS mypassword
LIST
quit

View file

@ -0,0 +1,26 @@
# This is the output of a manual test.
# CRAM-MD5 is not (yet) testable with telnet.
#
# # telnet localhost 25
# Trying ::1...
# Connected to localhost.
# Escape character is '^]'.
# 220 mail.my-domain.com ESMTP Postfix (Ubuntu)
# ehlo test
# 250-mail.my-domain.com
# 250-PIPELINING
# 250-SIZE 10240000
# 250-VRFY
# 250-ETRN
# 250-STARTTLS
# 250-AUTH PLAIN LOGIN CRAM-MD5 DIGEST-MD5
# 250-AUTH=PLAIN LOGIN CRAM-MD5 DIGEST-MD5
# 250-ENHANCEDSTATUSCODES
# 250-8BITMIME
# 250 DSN
# AUTH CRAM-MD5
# 334 PDIxMDMyODkzMTMuMTA2Mzg2MjhAbWFpbC5teS1kb21haW4uY29tPg==
# dXNlcjFAbG9jYWxob3N0LmxvY2FsZG9tYWluIGJlYjUxNzg4OGE5ZWI0OGM1NjQ2MTYwZGY3NTY1ZWNh
# 235 2.7.0 Authentication successful
# QUIT
# 221 2.0.0 Bye

View file

@ -0,0 +1,4 @@
EHLO mail
AUTH LOGIN dXNlcjFAbG9jYWxob3N0LmxvY2FsZG9tYWlu
Bn3JKisq4HQ2RO==
QUIT

View file

@ -0,0 +1,4 @@
EHLO mail
AUTH LOGIN dXNlcjFAbG9jYWxob3N0LmxvY2FsZG9tYWlu
bXlwYXNzd29yZA==
QUIT

View file

@ -0,0 +1,3 @@
EHLO mail
AUTH PLAIN WRONGPASSWORD
QUIT

View file

@ -0,0 +1,3 @@
EHLO mail
AUTH PLAIN dXNlcjFAbG9jYWxob3N0LmxvY2FsZG9tYWluAHVzZXIxQGxvY2FsaG9zdC5sb2NhbGRvbWFpbgBteXBhc3N3b3Jk
QUIT

142
test/bats/bats Executable file
View file

@ -0,0 +1,142 @@
#!/usr/bin/env bash
set -e
version() {
echo "Bats 0.4.0"
}
usage() {
version
echo "Usage: bats [-c] [-p | -t] <test> [<test> ...]"
}
help() {
usage
echo
echo " <test> is the path to a Bats test file, or the path to a directory"
echo " containing Bats test files."
echo
echo " -c, --count Count the number of test cases without running any tests"
echo " -h, --help Display this help message"
echo " -p, --pretty Show results in pretty format (default for terminals)"
echo " -t, --tap Show results in TAP format"
echo " -v, --version Display the version number"
echo
echo " For more information, see https://github.com/sstephenson/bats"
echo
}
resolve_link() {
$(type -p greadlink readlink | head -1) "$1"
}
abs_dirname() {
local cwd="$(pwd)"
local path="$1"
while [ -n "$path" ]; do
cd "${path%/*}"
local name="${path##*/}"
path="$(resolve_link "$name" || true)"
done
pwd
cd "$cwd"
}
expand_path() {
{ cd "$(dirname "$1")" 2>/dev/null
local dirname="$PWD"
cd "$OLDPWD"
echo "$dirname/$(basename "$1")"
} || echo "$1"
}
BATS_LIBEXEC="$(abs_dirname "$0")"
export BATS_PREFIX="$(abs_dirname "$BATS_LIBEXEC")"
export BATS_CWD="$(abs_dirname .)"
export PATH="$BATS_LIBEXEC:$PATH"
options=()
arguments=()
for arg in "$@"; do
if [ "${arg:0:1}" = "-" ]; then
if [ "${arg:1:1}" = "-" ]; then
options[${#options[*]}]="${arg:2}"
else
index=1
while option="${arg:$index:1}"; do
[ -n "$option" ] || break
options[${#options[*]}]="$option"
let index+=1
done
fi
else
arguments[${#arguments[*]}]="$arg"
fi
done
unset count_flag pretty
[ -t 0 ] && [ -t 1 ] && pretty="1"
[ -n "$CI" ] && pretty=""
for option in "${options[@]}"; do
case "$option" in
"h" | "help" )
help
exit 0
;;
"v" | "version" )
version
exit 0
;;
"c" | "count" )
count_flag="-c"
;;
"t" | "tap" )
pretty=""
;;
"p" | "pretty" )
pretty="1"
;;
* )
usage >&2
exit 1
;;
esac
done
if [ "${#arguments[@]}" -eq 0 ]; then
usage >&2
exit 1
fi
filenames=()
for filename in "${arguments[@]}"; do
if [ -d "$filename" ]; then
shopt -s nullglob
for suite_filename in "$(expand_path "$filename")"/*.bats; do
filenames["${#filenames[@]}"]="$suite_filename"
done
shopt -u nullglob
else
filenames["${#filenames[@]}"]="$(expand_path "$filename")"
fi
done
if [ "${#filenames[@]}" -eq 1 ]; then
command="bats-exec-test"
else
command="bats-exec-suite"
fi
if [ -n "$pretty" ]; then
extended_syntax_flag="-x"
formatter="bats-format-tap-stream"
else
extended_syntax_flag=""
formatter="cat"
fi
set -o pipefail execfail
exec "$command" $count_flag $extended_syntax_flag "${filenames[@]}" | "$formatter"

55
test/bats/bats-exec-suite Executable file
View file

@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -e
count_only_flag=""
if [ "$1" = "-c" ]; then
count_only_flag=1
shift
fi
extended_syntax_flag=""
if [ "$1" = "-x" ]; then
extended_syntax_flag="-x"
shift
fi
trap "kill 0; exit 1" int
count=0
for filename in "$@"; do
let count+="$(bats-exec-test -c "$filename")"
done
if [ -n "$count_only_flag" ]; then
echo "$count"
exit
fi
echo "1..$count"
status=0
offset=0
for filename in "$@"; do
index=0
{
IFS= read -r # 1..n
while IFS= read -r line; do
case "$line" in
"begin "* )
let index+=1
echo "${line/ $index / $(($offset + $index)) }"
;;
"ok "* | "not ok "* )
[ -n "$extended_syntax_flag" ] || let index+=1
echo "${line/ $index / $(($offset + $index)) }"
[ "${line:0:6}" != "not ok" ] || status=1
;;
* )
echo "$line"
;;
esac
done
} < <( bats-exec-test $extended_syntax_flag "$filename" )
offset=$(($offset + $index))
done
exit "$status"

346
test/bats/bats-exec-test Executable file
View file

@ -0,0 +1,346 @@
#!/usr/bin/env bash
set -e
set -E
set -T
BATS_COUNT_ONLY=""
if [ "$1" = "-c" ]; then
BATS_COUNT_ONLY=1
shift
fi
BATS_EXTENDED_SYNTAX=""
if [ "$1" = "-x" ]; then
BATS_EXTENDED_SYNTAX="$1"
shift
fi
BATS_TEST_FILENAME="$1"
if [ -z "$BATS_TEST_FILENAME" ]; then
echo "usage: bats-exec <filename>" >&2
exit 1
elif [ ! -f "$BATS_TEST_FILENAME" ]; then
echo "bats: $BATS_TEST_FILENAME does not exist" >&2
exit 1
else
shift
fi
BATS_TEST_DIRNAME="$(dirname "$BATS_TEST_FILENAME")"
BATS_TEST_NAMES=()
load() {
local name="$1"
local filename
if [ "${name:0:1}" = "/" ]; then
filename="${name}"
else
filename="$BATS_TEST_DIRNAME/${name}.bash"
fi
[ -f "$filename" ] || {
echo "bats: $filename does not exist" >&2
exit 1
}
source "${filename}"
}
run() {
local e E T oldIFS
[[ ! "$-" =~ e ]] || e=1
[[ ! "$-" =~ E ]] || E=1
[[ ! "$-" =~ T ]] || T=1
set +e
set +E
set +T
output="$("$@" 2>&1)"
status="$?"
oldIFS=$IFS
IFS=$'\n' lines=($output)
[ -z "$e" ] || set -e
[ -z "$E" ] || set -E
[ -z "$T" ] || set -T
IFS=$oldIFS
}
setup() {
true
}
teardown() {
true
}
skip() {
BATS_TEST_SKIPPED=${1:-1}
BATS_TEST_COMPLETED=1
exit 0
}
bats_test_begin() {
BATS_TEST_DESCRIPTION="$1"
if [ -n "$BATS_EXTENDED_SYNTAX" ]; then
echo "begin $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3
fi
setup
}
bats_test_function() {
local test_name="$1"
BATS_TEST_NAMES["${#BATS_TEST_NAMES[@]}"]="$test_name"
}
bats_capture_stack_trace() {
BATS_PREVIOUS_STACK_TRACE=( "${BATS_CURRENT_STACK_TRACE[@]}" )
BATS_CURRENT_STACK_TRACE=()
local test_pattern=" $BATS_TEST_NAME $BATS_TEST_SOURCE"
local setup_pattern=" setup $BATS_TEST_SOURCE"
local teardown_pattern=" teardown $BATS_TEST_SOURCE"
local frame
local index=1
while frame="$(caller "$index")"; do
BATS_CURRENT_STACK_TRACE["${#BATS_CURRENT_STACK_TRACE[@]}"]="$frame"
if [[ "$frame" = *"$test_pattern" || \
"$frame" = *"$setup_pattern" || \
"$frame" = *"$teardown_pattern" ]]; then
break
else
let index+=1
fi
done
BATS_SOURCE="$(bats_frame_filename "${BATS_CURRENT_STACK_TRACE[0]}")"
BATS_LINENO="$(bats_frame_lineno "${BATS_CURRENT_STACK_TRACE[0]}")"
}
bats_print_stack_trace() {
local frame
local index=1
local count="${#@}"
for frame in "$@"; do
local filename="$(bats_trim_filename "$(bats_frame_filename "$frame")")"
local lineno="$(bats_frame_lineno "$frame")"
if [ $index -eq 1 ]; then
echo -n "# ("
else
echo -n "# "
fi
local fn="$(bats_frame_function "$frame")"
if [ "$fn" != "$BATS_TEST_NAME" ]; then
echo -n "from function \`$fn' "
fi
if [ $index -eq $count ]; then
echo "in test file $filename, line $lineno)"
else
echo "in file $filename, line $lineno,"
fi
let index+=1
done
}
bats_print_failed_command() {
local frame="$1"
local status="$2"
local filename="$(bats_frame_filename "$frame")"
local lineno="$(bats_frame_lineno "$frame")"
local failed_line="$(bats_extract_line "$filename" "$lineno")"
local failed_command="$(bats_strip_string "$failed_line")"
echo -n "# \`${failed_command}' "
if [ $status -eq 1 ]; then
echo "failed"
else
echo "failed with status $status"
fi
}
bats_frame_lineno() {
local frame="$1"
local lineno="${frame%% *}"
echo "$lineno"
}
bats_frame_function() {
local frame="$1"
local rest="${frame#* }"
local fn="${rest%% *}"
echo "$fn"
}
bats_frame_filename() {
local frame="$1"
local rest="${frame#* }"
local filename="${rest#* }"
if [ "$filename" = "$BATS_TEST_SOURCE" ]; then
echo "$BATS_TEST_FILENAME"
else
echo "$filename"
fi
}
bats_extract_line() {
local filename="$1"
local lineno="$2"
sed -n "${lineno}p" "$filename"
}
bats_strip_string() {
local string="$1"
printf "%s" "$string" | sed -e "s/^[ "$'\t'"]*//" -e "s/[ "$'\t'"]*$//"
}
bats_trim_filename() {
local filename="$1"
local length="${#BATS_CWD}"
if [ "${filename:0:length+1}" = "${BATS_CWD}/" ]; then
echo "${filename:length+1}"
else
echo "$filename"
fi
}
bats_debug_trap() {
if [ "$BASH_SOURCE" != "$1" ]; then
bats_capture_stack_trace
fi
}
bats_error_trap() {
BATS_ERROR_STATUS="$?"
BATS_ERROR_STACK_TRACE=( "${BATS_PREVIOUS_STACK_TRACE[@]}" )
trap - debug
}
bats_teardown_trap() {
trap "bats_exit_trap" exit
local status=0
teardown >>"$BATS_OUT" 2>&1 || status="$?"
if [ $status -eq 0 ]; then
BATS_TEARDOWN_COMPLETED=1
elif [ -n "$BATS_TEST_COMPLETED" ]; then
BATS_ERROR_STATUS="$status"
BATS_ERROR_STACK_TRACE=( "${BATS_CURRENT_STACK_TRACE[@]}" )
fi
bats_exit_trap
}
bats_exit_trap() {
local status
local skipped
trap - err exit
skipped=""
if [ -n "$BATS_TEST_SKIPPED" ]; then
skipped=" # skip"
if [ "1" != "$BATS_TEST_SKIPPED" ]; then
skipped+=" ($BATS_TEST_SKIPPED)"
fi
fi
if [ -z "$BATS_TEST_COMPLETED" ] || [ -z "$BATS_TEARDOWN_COMPLETED" ]; then
echo "not ok $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3
bats_print_stack_trace "${BATS_ERROR_STACK_TRACE[@]}" >&3
bats_print_failed_command "${BATS_ERROR_STACK_TRACE[${#BATS_ERROR_STACK_TRACE[@]}-1]}" "$BATS_ERROR_STATUS" >&3
sed -e "s/^/# /" < "$BATS_OUT" >&3
status=1
else
echo "ok ${BATS_TEST_NUMBER}${skipped} ${BATS_TEST_DESCRIPTION}" >&3
status=0
fi
rm -f "$BATS_OUT"
exit "$status"
}
bats_perform_tests() {
echo "1..$#"
test_number=1
status=0
for test_name in "$@"; do
"$0" $BATS_EXTENDED_SYNTAX "$BATS_TEST_FILENAME" "$test_name" "$test_number" || status=1
let test_number+=1
done
exit "$status"
}
bats_perform_test() {
BATS_TEST_NAME="$1"
if [ "$(type -t "$BATS_TEST_NAME" || true)" = "function" ]; then
BATS_TEST_NUMBER="$2"
if [ -z "$BATS_TEST_NUMBER" ]; then
echo "1..1"
BATS_TEST_NUMBER="1"
fi
BATS_TEST_COMPLETED=""
BATS_TEARDOWN_COMPLETED=""
trap "bats_debug_trap \"\$BASH_SOURCE\"" debug
trap "bats_error_trap" err
trap "bats_teardown_trap" exit
"$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1
BATS_TEST_COMPLETED=1
else
echo "bats: unknown test name \`$BATS_TEST_NAME'" >&2
exit 1
fi
}
if [ -z "$TMPDIR" ]; then
BATS_TMPDIR="/tmp"
else
BATS_TMPDIR="${TMPDIR%/}"
fi
BATS_TMPNAME="$BATS_TMPDIR/bats.$$"
BATS_PARENT_TMPNAME="$BATS_TMPDIR/bats.$PPID"
BATS_OUT="${BATS_TMPNAME}.out"
bats_preprocess_source() {
BATS_TEST_SOURCE="${BATS_TMPNAME}.src"
{ tr -d '\r' < "$BATS_TEST_FILENAME"; echo; } | bats-preprocess > "$BATS_TEST_SOURCE"
trap "bats_cleanup_preprocessed_source" err exit
trap "bats_cleanup_preprocessed_source; exit 1" int
}
bats_cleanup_preprocessed_source() {
rm -f "$BATS_TEST_SOURCE"
}
bats_evaluate_preprocessed_source() {
if [ -z "$BATS_TEST_SOURCE" ]; then
BATS_TEST_SOURCE="${BATS_PARENT_TMPNAME}.src"
fi
source "$BATS_TEST_SOURCE"
}
exec 3<&1
if [ "$#" -eq 0 ]; then
bats_preprocess_source
bats_evaluate_preprocessed_source
if [ -n "$BATS_COUNT_ONLY" ]; then
echo "${#BATS_TEST_NAMES[@]}"
else
bats_perform_tests "${BATS_TEST_NAMES[@]}"
fi
else
bats_evaluate_preprocessed_source
bats_perform_test "$@"
fi

165
test/bats/bats-format-tap-stream Executable file
View file

@ -0,0 +1,165 @@
#!/usr/bin/env bash
set -e
# Just stream the TAP output (sans extended syntax) if tput is missing
command -v tput >/dev/null || exec grep -v "^begin "
header_pattern='[0-9]+\.\.[0-9]+'
IFS= read -r header
if [[ "$header" =~ $header_pattern ]]; then
count="${header:3}"
index=0
failures=0
skipped=0
name=""
count_column_width=$(( ${#count} * 2 + 2 ))
else
# If the first line isn't a TAP plan, print it and pass the rest through
printf "%s\n" "$header"
exec cat
fi
update_screen_width() {
screen_width="$(tput cols)"
count_column_left=$(( $screen_width - $count_column_width ))
}
trap update_screen_width WINCH
update_screen_width
begin() {
go_to_column 0
printf_with_truncation $(( $count_column_left - 1 )) " %s" "$name"
clear_to_end_of_line
go_to_column $count_column_left
printf "%${#count}s/${count}" "$index"
go_to_column 1
}
pass() {
go_to_column 0
printf " ✓ %s" "$name"
advance
}
skip() {
local reason="$1"
[ -z "$reason" ] || reason=": $reason"
go_to_column 0
printf " - %s (skipped%s)" "$name" "$reason"
advance
}
fail() {
go_to_column 0
set_color 1 bold
printf " ✗ %s" "$name"
advance
}
log() {
set_color 1
printf " %s\n" "$1"
clear_color
}
summary() {
printf "\n%d test%s" "$count" "$(plural "$count")"
printf ", %d failure%s" "$failures" "$(plural "$failures")"
if [ "$skipped" -gt 0 ]; then
printf ", %d skipped" "$skipped"
fi
printf "\n"
}
printf_with_truncation() {
local width="$1"
shift
local string="$(printf "$@")"
if [ "${#string}" -gt "$width" ]; then
printf "%s..." "${string:0:$(( $width - 4 ))}"
else
printf "%s" "$string"
fi
}
go_to_column() {
local column="$1"
printf "\x1B[%dG" $(( $column + 1 ))
}
clear_to_end_of_line() {
printf "\x1B[K"
}
advance() {
clear_to_end_of_line
echo
clear_color
}
set_color() {
local color="$1"
local weight="$2"
printf "\x1B[%d;%dm" $(( 30 + $color )) "$( [ "$weight" = "bold" ] && echo 1 || echo 22 )"
}
clear_color() {
printf "\x1B[0m"
}
plural() {
[ "$1" -eq 1 ] || echo "s"
}
_buffer=""
buffer() {
_buffer="${_buffer}$("$@")"
}
flush() {
printf "%s" "$_buffer"
_buffer=""
}
finish() {
flush
printf "\n"
}
trap finish EXIT
while IFS= read -r line; do
case "$line" in
"begin "* )
let index+=1
name="${line#* $index }"
buffer begin
flush
;;
"ok "* )
skip_expr="ok $index # skip (\(([^)]*)\))?"
if [[ "$line" =~ $skip_expr ]]; then
let skipped+=1
buffer skip "${BASH_REMATCH[2]}"
else
buffer pass
fi
;;
"not ok "* )
let failures+=1
buffer fail
;;
"# "* )
buffer log "${line:2}"
;;
esac
done
buffer summary

52
test/bats/bats-preprocess Executable file
View file

@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -e
encode_name() {
local name="$1"
local result="test_"
if [[ ! "$name" =~ [^[:alnum:]\ _-] ]]; then
name="${name//_/-5f}"
name="${name//-/-2d}"
name="${name// /_}"
result+="$name"
else
local length="${#name}"
local char i
for ((i=0; i<length; i++)); do
char="${name:$i:1}"
if [ "$char" = " " ]; then
result+="_"
elif [[ "$char" =~ [[:alnum:]] ]]; then
result+="$char"
else
result+="$(printf -- "-%02x" \'"$char")"
fi
done
fi
echo "$result"
}
tests=()
index=0
pattern='^ *@test *([^ ].*) *\{ *(.*)$'
while IFS= read -r line; do
let index+=1
if [[ "$line" =~ $pattern ]]; then
quoted_name="${BASH_REMATCH[1]}"
body="${BASH_REMATCH[2]}"
name="$(eval echo "$quoted_name")"
encoded_name="$(encode_name "$name")"
tests["${#tests[@]}"]="$encoded_name"
echo "${encoded_name}() { bats_test_begin ${quoted_name} ${index}; ${body}"
else
printf "%s\n" "$line"
fi
done
for test_name in "${tests[@]}"; do
echo "bats_test_function ${test_name}"
done

View file

@ -0,0 +1,14 @@
HELO mail.external.tld
MAIL FROM: spam@external.tld
RCPT TO: user1@localhost.localdomain
DATA
From: Docker Mail Server <dockermailserver@external.tld>
To: Existing Local User <user1@localhost.localdomain>
Date: Sat, 22 May 2010 07:43:25 -0400
Subject: Test Message
This is a test mail.
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
.
QUIT

View file

@ -0,0 +1,33 @@
HELO mail.external.tld
MAIL FROM: virus@external.tld
RCPT TO: user1@localhost.localdomain
DATA
From: Docker Mail Server <dockermailserver@external.tld>
To: Existing Local User <user1@localhost.localdomain>
Date: Sat, 22 May 2010 07:43:25 -0400
Subject: Test Message
Content-type: multipart/mixed; boundary="emailboundary"
MIME-version: 1.0
This is a multi-part message in MIME format.
--emailboundary
Content-type: text/plain
This is the body of the message.
--emailboundary
Content-type: application/octet-stream
Content-transfer-encoding: base64
UEsDBAoAAAAAAOCYuCg8z1FoRAAAAEQAAAAJAAAAZWljYXIuY29tWDVPIVAlQEFQ
WzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNU
LUZJTEUhJEgrSCpQSwECFAAKAAAAAADgmLgoPM9RaEQAAABEAAAACQAAAAAAAAAB
ACAA/4EAAAAAZWljYXIuY29tUEsFBgAAAAABAAEANwAAAGsAAAAAAA==
--emailboundary--
.
QUIT

View file

@ -0,0 +1,12 @@
HELO mail.external.tld
MAIL FROM: user@external.tld
RCPT TO: alias1@localhost.localdomain
DATA
From: Docker Mail Server <dockermailserver@external.tld>
To: Existing Local User <alias1@localhost.localdomain>
Date: Sat, 22 May 2010 07:43:25 -0400
Subject: Test Message
This is a test mail.
.
QUIT

View file

@ -0,0 +1,12 @@
HELO mail.external.tld
MAIL FROM: user@external.tld
RCPT TO: alias2@localhost.localdomain
DATA
From: Docker Mail Server <dockermailserver@external.tld>
To: Existing Local User <alias2@localhost.localdomain>
Date: Sat, 22 May 2010 07:43:25 -0400
Subject: Test Message
This is a test mail.
.
QUIT

View file

@ -0,0 +1,12 @@
HELO mail.external.tld
MAIL FROM: user@external.tld
RCPT TO: user1@localhost.localdomain
DATA
From: Docker Mail Server <dockermailserver@external.tld>
To: Existing Local User <user1@localhost.localdomain>
Date: Sat, 22 May 2010 07:43:25 -0400
Subject: Test Message
This is a test mail.
.
QUIT

View file

@ -0,0 +1,12 @@
HELO mail.external.tld
MAIL FROM: user@external.tld
RCPT TO: nouser@localhost.localdomain
DATA
From: Docker Mail Server <dockermailserver@external.tld>
To: Existing Local User <user1@localhost.localdomain>
Date: Sat, 22 May 2010 07:43:25 -0400
Subject: Test Message
This is a test mail.
.
QUIT

2
test/main.cf Normal file
View file

@ -0,0 +1,2 @@
max_idle = 600s
readme_directory = /tmp

383
test/tests.bats Normal file
View file

@ -0,0 +1,383 @@
#
# processes
#
@test "checking process: postfix" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/lib/postfix/master'"
[ "$status" -eq 0 ]
}
@test "checking process: saslauthd" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/saslauthd'"
[ "$status" -eq 0 ]
}
@test "checking process: clamd" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
[ "$status" -eq 0 ]
}
@test "checking process: new" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/amavisd-new'"
[ "$status" -eq 0 ]
}
@test "checking process: opendkim" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/opendkim'"
[ "$status" -eq 0 ]
}
@test "checking process: opendmarc" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/opendmarc'"
[ "$status" -eq 0 ]
}
@test "checking process: fail2ban (disabled in default configuration)" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/bin/python /usr/bin/fail2ban-server'"
[ "$status" -eq 1 ]
}
@test "checking process: fail2ban (fail2ban server enabled)" {
run docker exec mail_fail2ban /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/bin/python /usr/bin/fail2ban-server'"
[ "$status" -eq 0 ]
}
@test "checking process: courierpop3d (disabled in default configuration)" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/lib/courier/courier/courierpop3d'"
[ "$status" -eq 1 ]
}
@test "checking process: courierpop3d (pop3 server enabled)" {
run docker exec mail_pop3 /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/lib/courier/courier/courierpop3d'"
[ "$status" -eq 0 ]
}
@test "checking process: courierpop3d (disabled using SMTP_ONLY)" {
run docker exec mail_smtponly /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/lib/courier/courier/courierpop3d'"
[ "$status" -eq 1 ]
}
#
# imap
#
@test "checking process: courier imaplogin (enabled in default configuration)" {
run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/lib/courier/courier/imaplogin'"
[ "$status" -eq 0 ]
}
@test "checking process: courier imaplogin (disabled using SMTP_ONLY)" {
run docker exec mail_smtponly /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/lib/courier/courier/imaplogin'"
[ "$status" -eq 1 ]
}
@test "checking imap: server is ready with STARTTLS" {
run docker exec mail /bin/bash -c "nc -w 1 0.0.0.0 143 | grep '* OK' | grep 'STARTTLS' | grep 'Courier-IMAP ready'"
[ "$status" -eq 0 ]
}
@test "checking imap: authentication works" {
run docker exec mail /bin/sh -c "nc -w 1 0.0.0.0 143 < /tmp/test/auth/imap-auth.txt"
[ "$status" -eq 0 ]
}
#
# 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'"
[ "$status" -eq 0 ]
}
@test "checking pop: authentication works" {
run docker exec mail_pop3 /bin/sh -c "nc -w 1 0.0.0.0 110 < /tmp/test/auth/pop3-auth.txt"
[ "$status" -eq 0 ]
}
#
# sasl
#
@test "checking sasl: testsaslauthd works with good password" {
run docker exec mail /bin/sh -c "testsaslauthd -u user2 -r otherdomain.tld -p mypassword | grep 'OK \"Success.\"'"
[ "$status" -eq 0 ]
}
@test "checking sasl: testsaslauthd fails with bad password" {
run docker exec mail /bin/sh -c "testsaslauthd -u user2 -r otherdomain.tld -p BADPASSWORD | grep 'NO \"authentication failed\"'"
[ "$status" -eq 0 ]
}
@test "checking sasl: sasl_passwd.db exists" {
run docker exec mail [ -f /etc/postfix/sasl_passwd.db ]
[ "$status" -eq 0 ]
}
#
# 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 'clamav|freshclam|mail'|wc -l"
[ "$status" -eq 0 ]
[ "$output" = 3 ]
}
#
# smtp
#
@test "checking smtp: authentication works with good password (plain)" {
run docker exec mail /bin/sh -c "nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-plain.txt | grep 'Authentication successful'"
[ "$status" -eq 0 ]
}
@test "checking smtp: authentication fails with wrong password (plain)" {
run docker exec mail /bin/sh -c "nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-plain-wrong.txt | grep 'authentication failed'"
[ "$status" -eq 0 ]
}
@test "checking smtp: authentication works with good password (login)" {
run docker exec mail /bin/sh -c "nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-login.txt | grep 'Authentication successful'"
[ "$status" -eq 0 ]
}
@test "checking smtp: authentication fails with wrong password (login)" {
run docker exec mail /bin/sh -c "nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-login-wrong.txt | grep 'authentication failed'"
[ "$status" -eq 0 ]
}
@test "checking smtp: delivers mail to existing account" {
run docker exec mail /bin/sh -c "grep 'status=sent (delivered to maildir)' /var/log/mail/mail.log | wc -l"
[ "$status" -eq 0 ]
[ "$output" -eq 2 ]
}
@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"
[ "$status" -eq 0 ]
[ "$output" = 1 ]
}
@test "checking smtp: user1 should have received 2 mails" {
run docker exec mail /bin/sh -c "ls -A /var/mail/localhost.localdomain/user1/new | wc -l"
[ "$status" -eq 0 ]
[ "$output" = 2 ]
}
@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"
[ "$status" -eq 0 ]
[ "$output" = 1 ]
}
@test "checking smtp: redirects mail to external alias" {
run docker exec mail /bin/sh -c "grep -- '-> <external1@otherdomain.tld>' /var/log/mail/mail.log | wc -l"
[ "$status" -eq 0 ]
[ "$output" = 1 ]
}
@test "checking smtp: rejects spam" {
run docker exec mail /bin/sh -c "grep 'Blocked SPAM' /var/log/mail/mail.log | grep spam@external.tld | wc -l"
[ "$status" -eq 0 ]
[ "$output" = 1 ]
}
@test "checking smtp: rejects virus" {
run docker exec mail /bin/sh -c "grep 'Blocked INFECTED' /var/log/mail/mail.log | grep virus@external.tld | wc -l"
[ "$status" -eq 0 ]
[ "$output" = 1 ]
}
#
# accounts
#
@test "checking accounts: user accounts" {
run docker exec mail sasldblistusers2
[ "$status" -eq 0 ]
[ "${lines[0]}" = "user1@localhost.localdomain: userPassword" ]
[ "${lines[1]}" = "user2@otherdomain.tld: userPassword" ]
}
@test "checking accounts: user mail folders for user1" {
run docker exec mail ls -A /var/mail/localhost.localdomain/user1
[ "$status" -eq 0 ]
[ "${lines[0]}" = ".Drafts" ]
[ "${lines[1]}" = ".Sent" ]
[ "${lines[2]}" = ".Trash" ]
[ "${lines[3]}" = "courierimapsubscribed" ]
[ "${lines[4]}" = "cur" ]
[ "${lines[5]}" = "new" ]
[ "${lines[6]}" = "tmp" ]
}
@test "checking accounts: user mail folders for user2" {
run docker exec mail ls -A /var/mail/otherdomain.tld/user2
[ "$status" -eq 0 ]
[ "${lines[0]}" = ".Drafts" ]
[ "${lines[1]}" = ".Sent" ]
[ "${lines[2]}" = ".Trash" ]
[ "${lines[3]}" = "courierimapsubscribed" ]
[ "${lines[4]}" = "cur" ]
[ "${lines[5]}" = "new" ]
[ "${lines[6]}" = "tmp" ]
}
#
# postfix
#
@test "checking postfix: vhost file is correct" {
run docker exec mail cat /etc/postfix/vhost
[ "$status" -eq 0 ]
[ "${lines[0]}" = "localhost.localdomain" ]
[ "${lines[1]}" = "otherdomain.tld" ]
}
@test "checking postfix: main.cf overrides" {
run docker exec mail grep -q 'max_idle = 600s' /tmp/postfix/main.cf
[ "$status" -eq 0 ]
run docker exec mail grep -q 'readme_directory = /tmp' /tmp/postfix/main.cf
[ "$status" -eq 0 ]
}
#
# 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'"
[ "$status" -eq 0 ]
run docker exec mail_pop3 /bin/sh -c "grep '\$sa_tag2_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 6.31'"
[ "$status" -eq 0 ]
run docker exec mail_pop3 /bin/sh -c "grep '\$sa_kill_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 6.31'"
[ "$status" -eq 0 ]
}
@test "checking spamassassin: docker env variables are set correctly (custom)" {
run docker exec mail /bin/sh -c "grep '\$sa_tag_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 1.0'"
[ "$status" -eq 0 ]
run docker exec mail /bin/sh -c "grep '\$sa_tag2_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 2.0'"
[ "$status" -eq 0 ]
run docker exec mail /bin/sh -c "grep '\$sa_kill_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 3.0'"
[ "$status" -eq 0 ]
}
#
# opendkim
#
@test "checking opendkim: /etc/opendkim/KeyTable should contain 2 entries" {
run docker exec mail /bin/sh -c "cat /etc/opendkim/KeyTable | wc -l"
[ "$status" -eq 0 ]
[ "$output" -eq 2 ]
}
@test "checking opendkim: /etc/opendkim/keys/ should contain 2 entries" {
run docker exec mail /bin/sh -c "ls -l /etc/opendkim/keys/ | grep '^d' | wc -l"
[ "$status" -eq 0 ]
[ "$output" -eq 2 ]
}
#
# opendmarc
#
@test "checking opendkim: server fqdn should be added to /etc/opendmarc.conf as AuthservID" {
run docker exec mail grep ^AuthservID /etc/opendmarc.conf
[ "$status" -eq 0 ]
[ "$output" = "AuthservID mail.my-domain.com" ]
}
@test "checking opendkim: server fqdn should be added to /etc/opendmarc.conf as TrustedAuthservIDs" {
run docker exec mail grep ^TrustedAuthservID /etc/opendmarc.conf
[ "$status" -eq 0 ]
[ "$output" = "TrustedAuthservIDs mail.my-domain.com" ]
}
#
# letsencrypt
#
@test "checking letsencrypt: lets-encrypt-x1-cross-signed.pem is installed" {
run docker exec mail grep 'BEGIN CERTIFICATE' /etc/ssl/certs/lets-encrypt-x1-cross-signed.pem
[ "$status" -eq 0 ]
}
@test "checking letsencrypt: lets-encrypt-x2-cross-signed.pem is installed" {
run docker exec mail grep 'BEGIN CERTIFICATE' /etc/ssl/certs/lets-encrypt-x2-cross-signed.pem
[ "$status" -eq 0 ]
}
#
# ssl
#
@test "checking ssl: generated default cert is installed" {
run docker exec mail /bin/sh -c "openssl s_client -connect 0.0.0.0:587 -starttls smtp -CApath /etc/ssl/certs/ | grep 'Verify return code: 0 (ok)'"
[ "$status" -eq 0 ]
}
#
# fail2ban
#
@test "checking fail2ban: localhost is not banned" {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status sasl | grep 'IP list:.*127.0.0.1'"
[ "$status" -eq 1 ]
}
@test "checking fail2ban: ban ip on multiple failed login" {
docker exec mail_fail2ban fail2ban-client status sasl
docker exec mail_fail2ban fail2ban-client set sasl delignoreip 127.0.0.1/8
docker exec mail_fail2ban /bin/sh -c 'nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-login-wrong.txt'
docker exec mail_fail2ban /bin/sh -c 'nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-login-wrong.txt'
docker exec mail_fail2ban /bin/sh -c 'nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-login-wrong.txt'
sleep 5
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status sasl | grep 'IP list:.*127.0.0.1'"
[ "$status" -eq 0 ]
}
@test "checking fail2ban: unban ip works" {
docker exec mail_fail2ban fail2ban-client set sasl addignoreip 127.0.0.1/8
docker exec mail_fail2ban fail2ban-client set sasl unbanip 127.0.0.1
sleep 5
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status sasl | grep 'IP list:.*127.0.0.1'"
[ "$status" -eq 1 ]
}
#
# system
#
@test "checking system: freshclam cron is enabled" {
run docker exec mail crontab -l
[ "$status" -eq 0 ]
[ "$output" = "0 1 * * * /usr/bin/freshclam --quiet" ]
}
@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
[ "$status" -eq 1 ]
run docker exec mail grep ': error:' /var/log/mail/mail.log
[ "$status" -eq 1 ]
run docker exec mail_pop3 grep 'non-null host address bits in' /var/log/mail/mail.log
[ "$status" -eq 1 ]
run docker exec mail_pop3 grep ': error:' /var/log/mail/mail.log
[ "$status" -eq 1 ]
}
@test "checking system: sets the server fqdn" {
run docker exec mail hostname
[ "$status" -eq 0 ]
[ "$output" = "mail.my-domain.com" ]
}
@test "checking system: sets the server domain name in /etc/mailname" {
run docker exec mail cat /etc/mailname
[ "$status" -eq 0 ]
[ "$output" = "my-domain.com" ]
}

2
test/virtual Normal file
View file

@ -0,0 +1,2 @@
alias1@localhost.localdomain user1@localhost.localdomain
alias2@localhost.localdomain external1@otherdomain.tld