[DOCKER][MAIL] Setup docker mail server

This commit is contained in:
Pastilhas 2020-10-20 22:11:54 +01:00 committed by Hugo Sales
parent 12cfb5006a
commit 27065e5ead
80 changed files with 1775 additions and 60 deletions

13
dockermail/.env Normal file
View File

@ -0,0 +1,13 @@
MYSQL_DATABASE=mailserver
MYSQL_USER=mailserver
MYSQL_PASSWORD=changeme
MYSQL_ROOT_PASSWORD=changeme
MAILNAME=mail.example.com
POSTMASTER=postmaster@example.com
RELAYHOST=false
FILTER_MIME=false
FILTER_VIRUS=true
ENABLE_IMAP=true
ENABLE_POP3=true
CONTROLLER_PASSWORD=changeme
WAITSTART_TIMEOUT=2m

21
dockermail/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Jeffrey Boehm
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.

View File

@ -1,6 +1,9 @@
To start, run setup.sh choose name and domain and create the first email account. Then open mailserver/ and run 'docker-compose up'
To change settings read mailserver/env-mailserver
To update, run update.sh
Add more accounts with add_account.sh and send mails with send_mail.sh
POP3 STARTTLS 127.0.0.1:110
POP3S 127.0.0.1:995
IMAP STARTTLS 127.0.0.1:143
IMAPS 127.0.0.1:993
SMTP 127.0.0.1:25
Mail Submission STARTTLS 127.0.0.1:587
Management Interface 127.0.0.1:81/manager/
Webmail 127.0.0.1:81/webmail/
Rspamd interface 127.0.0.1:81/rspamd/

View File

@ -1,8 +0,0 @@
#!/bin/bash
read -p "EMAIL: " user
read -s -p "PASS: " password
printf "\n"
bash mailserver/setup.sh email add "$user" "$password"

10
dockermail/db/Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM mysql:5.7
LABEL maintainer="jeff@ressourcenkonflikt.de"
ENV MYSQL_DATABASE=mailserver \
MYSQL_USER=mailserver \
MYSQL_PASSWORD=changeme \
MYSQL_ROOT_PASSWORD=changeme
COPY rootfs/ /
VOLUME /run/mysqld

View File

@ -0,0 +1,82 @@
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# Export von Tabelle mail_aliases
# ------------------------------------------------------------
DROP TABLE IF EXISTS `mail_aliases`;
CREATE TABLE `mail_aliases` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain_id` int(11) DEFAULT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`destination` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `alias_idx` (`domain_id`,`name`,`destination`),
KEY `IDX_85AF3A56115F0EE5` (`domain_id`),
CONSTRAINT `FK_5F12BB39115F0EE5` FOREIGN KEY (`domain_id`) REFERENCES `mail_domains` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
# Export von Tabelle mail_domains
# ------------------------------------------------------------
DROP TABLE IF EXISTS `mail_domains`;
CREATE TABLE `mail_domains` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_56C63EF25E237E06` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# Export von Tabelle mail_users
# ------------------------------------------------------------
DROP TABLE IF EXISTS `mail_users`;
CREATE TABLE `mail_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain_id` int(11) DEFAULT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_idx` (`name`,`domain_id`),
KEY `IDX_20400786115F0EE5` (`domain_id`),
CONSTRAINT `FK_1483A5E9115F0EE5` FOREIGN KEY (`domain_id`) REFERENCES `mail_domains` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
# Export von Tabelle migration_versions
# ------------------------------------------------------------
DROP TABLE IF EXISTS `migration_versions`;
CREATE TABLE `migration_versions` (
`version` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
LOCK TABLES `migration_versions` WRITE;
/*!40000 ALTER TABLE `migration_versions` DISABLE KEYS */;
INSERT INTO `migration_versions` (`version`)
VALUES
('20180320164351'),
('20180320171339');
/*!40000 ALTER TABLE `migration_versions` ENABLE KEYS */;
UNLOCK TABLES;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

View File

@ -0,0 +1,226 @@
-- Roundcube Webmail initial database structure
/*!40014 SET FOREIGN_KEY_CHECKS=0 */;
-- Table structure for table `session`
CREATE TABLE `session` (
`sess_id` varchar(128) NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`ip` varchar(40) NOT NULL,
`vars` mediumtext NOT NULL,
PRIMARY KEY(`sess_id`),
INDEX `changed_index` (`changed`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `users`
CREATE TABLE `users` (
`user_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`username` varchar(128) BINARY NOT NULL,
`mail_host` varchar(128) NOT NULL,
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`last_login` datetime DEFAULT NULL,
`failed_login` datetime DEFAULT NULL,
`failed_login_counter` int(10) UNSIGNED DEFAULT NULL,
`language` varchar(5),
`preferences` longtext,
PRIMARY KEY(`user_id`),
UNIQUE `username` (`username`, `mail_host`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache`
CREATE TABLE `cache` (
`user_id` int(10) UNSIGNED NOT NULL,
`cache_key` varchar(128) BINARY NOT NULL,
`expires` datetime DEFAULT NULL,
`data` longtext NOT NULL,
PRIMARY KEY (`user_id`, `cache_key`),
CONSTRAINT `user_id_fk_cache` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `expires_index` (`expires`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_shared`
CREATE TABLE `cache_shared` (
`cache_key` varchar(255) BINARY NOT NULL,
`expires` datetime DEFAULT NULL,
`data` longtext NOT NULL,
PRIMARY KEY (`cache_key`),
INDEX `expires_index` (`expires`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_index`
CREATE TABLE `cache_index` (
`user_id` int(10) UNSIGNED NOT NULL,
`mailbox` varchar(255) BINARY NOT NULL,
`expires` datetime DEFAULT NULL,
`valid` tinyint(1) NOT NULL DEFAULT '0',
`data` longtext NOT NULL,
CONSTRAINT `user_id_fk_cache_index` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `expires_index` (`expires`),
PRIMARY KEY (`user_id`, `mailbox`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_thread`
CREATE TABLE `cache_thread` (
`user_id` int(10) UNSIGNED NOT NULL,
`mailbox` varchar(255) BINARY NOT NULL,
`expires` datetime DEFAULT NULL,
`data` longtext NOT NULL,
CONSTRAINT `user_id_fk_cache_thread` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `expires_index` (`expires`),
PRIMARY KEY (`user_id`, `mailbox`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_messages`
CREATE TABLE `cache_messages` (
`user_id` int(10) UNSIGNED NOT NULL,
`mailbox` varchar(255) BINARY NOT NULL,
`uid` int(11) UNSIGNED NOT NULL DEFAULT '0',
`expires` datetime DEFAULT NULL,
`data` longtext NOT NULL,
`flags` int(11) NOT NULL DEFAULT '0',
CONSTRAINT `user_id_fk_cache_messages` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `expires_index` (`expires`),
PRIMARY KEY (`user_id`, `mailbox`, `uid`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `contacts`
CREATE TABLE `contacts` (
`contact_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`del` tinyint(1) NOT NULL DEFAULT '0',
`name` varchar(128) NOT NULL DEFAULT '',
`email` text NOT NULL,
`firstname` varchar(128) NOT NULL DEFAULT '',
`surname` varchar(128) NOT NULL DEFAULT '',
`vcard` longtext NULL,
`words` text NULL,
`user_id` int(10) UNSIGNED NOT NULL,
PRIMARY KEY(`contact_id`),
CONSTRAINT `user_id_fk_contacts` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `user_contacts_index` (`user_id`,`del`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `contactgroups`
CREATE TABLE `contactgroups` (
`contactgroup_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(10) UNSIGNED NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`del` tinyint(1) NOT NULL DEFAULT '0',
`name` varchar(128) NOT NULL DEFAULT '',
PRIMARY KEY(`contactgroup_id`),
CONSTRAINT `user_id_fk_contactgroups` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `contactgroups_user_index` (`user_id`,`del`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE `contactgroupmembers` (
`contactgroup_id` int(10) UNSIGNED NOT NULL,
`contact_id` int(10) UNSIGNED NOT NULL,
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
PRIMARY KEY (`contactgroup_id`, `contact_id`),
CONSTRAINT `contactgroup_id_fk_contactgroups` FOREIGN KEY (`contactgroup_id`)
REFERENCES `contactgroups`(`contactgroup_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `contact_id_fk_contacts` FOREIGN KEY (`contact_id`)
REFERENCES `contacts`(`contact_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `contactgroupmembers_contact_index` (`contact_id`)
) /*!40000 ENGINE=INNODB */;
-- Table structure for table `identities`
CREATE TABLE `identities` (
`identity_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(10) UNSIGNED NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`del` tinyint(1) NOT NULL DEFAULT '0',
`standard` tinyint(1) NOT NULL DEFAULT '0',
`name` varchar(128) NOT NULL,
`organization` varchar(128) NOT NULL DEFAULT '',
`email` varchar(128) NOT NULL,
`reply-to` varchar(128) NOT NULL DEFAULT '',
`bcc` varchar(128) NOT NULL DEFAULT '',
`signature` longtext,
`html_signature` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY(`identity_id`),
CONSTRAINT `user_id_fk_identities` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `user_identities_index` (`user_id`, `del`),
INDEX `email_identities_index` (`email`, `del`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `dictionary`
CREATE TABLE `dictionary` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, -- redundant, for compat. with Galera Cluster
`user_id` int(10) UNSIGNED DEFAULT NULL, -- NULL here is for "shared dictionaries"
`language` varchar(5) NOT NULL,
`data` longtext NOT NULL,
CONSTRAINT `user_id_fk_dictionary` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE `uniqueness` (`user_id`, `language`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `searches`
CREATE TABLE `searches` (
`search_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(10) UNSIGNED NOT NULL,
`type` int(3) NOT NULL DEFAULT '0',
`name` varchar(128) NOT NULL,
`data` text,
PRIMARY KEY(`search_id`),
CONSTRAINT `user_id_fk_searches` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE `uniqueness` (`user_id`, `type`, `name`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `filestore`
CREATE TABLE `filestore` (
`file_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(10) UNSIGNED NOT NULL,
`context` varchar(32) NOT NULL,
`filename` varchar(128) NOT NULL,
`mtime` int(10) NOT NULL,
`data` longtext NOT NULL,
PRIMARY KEY (`file_id`),
CONSTRAINT `user_id_fk_filestore` FOREIGN KEY (`user_id`)
REFERENCES `users` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE `uniqueness` (`user_id`, `context`, `filename`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `system`
CREATE TABLE `system` (
`name` varchar(64) NOT NULL,
`value` mediumtext,
PRIMARY KEY(`name`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
/*!40014 SET FOREIGN_KEY_CHECKS=1 */;
INSERT INTO `system` (`name`, `value`) VALUES ('roundcube-version', '2019092900');

View File

@ -0,0 +1,125 @@
version: '3.5'
services:
# Creates self signed tls certificates. Remove if you
# use your own.
ssl:
image: jeboehm/mailserver-ssl:latest
build: ./ssl
env_file: .env
volumes:
- data-tls:/media/tls:rw
# Responsible for storing users and their aliases. Remove
# if you already have a MySQL server.
db:
image: jeboehm/mailserver-db:latest
build: ./db
restart: on-failure:5
env_file: .env
volumes:
- data-db:/var/lib/mysql
# The Mail Transfer Agent (Postfix) receives incoming mail
# on TCP port 25.
mta:
image: jeboehm/mailserver-mta:latest
build: ./mta
restart: on-failure:5
env_file: .env
volumes:
- data-tls:/media/tls:ro
# For using external certificates uncomment the following lines
# and change the path on the left side of the colon.
# - /home/user/certs/mail.example.com.crt:/media/tls/mailserver.crt:ro
# - /home/user/certs/mail.example.com.key:/media/tls/mailserver.key:ro
ports:
- "0.0.0.0:25:25"
# The Mail Delivery Agent (Dovecot) is responsible for storing
# incoming mail into a users mailbox and also delivers them
# via POP3 or IMAP4.
mda:
image: jeboehm/mailserver-mda:latest
build: ./mda
restart: on-failure:5
env_file: .env
volumes:
- data-mail:/var/vmail
- data-tls:/media/tls:ro
# For using external certificates uncomment the following lines
# and change the path on the left side of the colon.
# - /home/user/certs/mail.example.com.crt:/media/tls/mailserver.crt:ro
# - /home/user/certs/mail.example.com.key:/media/tls/mailserver.key:ro
ports:
- "0.0.0.0:143:143"
- "0.0.0.0:993:993"
- "0.0.0.0:110:110"
- "0.0.0.0:995:995"
- "0.0.0.0:587:587"
# The admin (mailserver-admin) and webmail (roundcube) interfaces
# live here. Can be removed if not needed.
web:
image: jeboehm/mailserver-web:latest
build: ./web
restart: on-failure:5
env_file: .env
volumes:
- data-dkim:/media/dkim
# For use with jwilder/nginx-proxy.
# environment:
# - VIRTUAL_HOST=mail.example.com
ports:
- "0.0.0.0:81:80"
# Incoming spam is (hopefully) filtered by rspamd which runs
# in this service.
filter:
image: jeboehm/mailserver-filter:latest
build: ./filter
restart: on-failure:5
env_file: .env
volumes:
- data-filter:/var/lib/rspamd
- data-dkim:/media/dkim
links:
- virus:virus.local
# Incoming viruses or malware is detected and rejected by
# this service. Can be removed if FILTER_VIRUS is set to false.
virus:
image: jeboehm/mailserver-virus:latest
build: ./virus
restart: on-failure:5
env_file: .env
volumes:
- data-virusdb:/var/lib/clamav
# If you want unhealthy containers to be restarted automatically
# just uncomment the following lines.
# autoheal:
# image: willfarrell/autoheal:latest
# restart: always
# networks: []
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock:ro
# environment:
# - AUTOHEAL_CONTAINER_LABEL=de.ressourcenkonflikt.docker-mailserver.autoheal
# Optional service: extend ClamAV (used in the virus service)
# by downloading additional databases provided by different
# companys. Run it regulary.
# virus_unof_sig_updater:
# build: ./virus/contrib/unofficial-sigs
# env_file: .env
# volumes:
# - data-virusdb:/var/lib/clamav
volumes:
data-db:
data-dkim:
data-mail:
data-tls:
data-filter:
data-virusdb:

View File

@ -0,0 +1,45 @@
ARG DOCKERIZE_VER=0.6.0
ARG ALPINE_VER=3.9
FROM jwilder/dockerize:${DOCKERIZE_VER} AS dockerize
FROM alpine:${ALPINE_VER}
LABEL maintainer="jeff@ressourcenkonflikt.de"
LABEL vendor="https://github.com/jeboehm/docker-mailserver"
LABEL de.ressourcenkonflikt.docker-mailserver.autoheal="true"
ENV FILTER_VIRUS=false \
FILTER_VIRUS_HOST=virus.local \
WAITSTART_TIMEOUT=1m \
CONTROLLER_PASSWORD=changeme
RUN apk --no-cache add \
openssl \
rspamd \
rspamd-client \
rspamd-controller \
rspamd-proxy && \
mkdir /run/rspamd && \
touch \
/etc/rspamd/local.d/antivirus.conf \
/etc/rspamd/local.d/worker-controller.inc && \
chown -R rspamd \
/run/rspamd \
/var/lib/rspamd \
/etc/rspamd/local.d/antivirus.conf \
/etc/rspamd/local.d/worker-controller.inc && \
wget -O /usr/share/rspamd/bayes.spam.sqlite https://rspamd.com/rspamd_statistics/bayes.spam.sqlite && \
wget -O /usr/share/rspamd/bayes.ham.sqlite https://rspamd.com/rspamd_statistics/bayes.ham.sqlite && \
apk --no-cache del \
openssl
COPY --from=dockerize /usr/local/bin/dockerize /usr/local/bin
COPY rootfs/ /
EXPOSE 11332 11334
USER rspamd
VOLUME ["/var/lib/rspamd"]
HEALTHCHECK CMD wget -O- -T 10 http://127.0.0.1:11334/stat
CMD ["/usr/local/bin/entrypoint.sh"]

View File

@ -0,0 +1,11 @@
{{ $filter_virus := eq (or ($.Env.FILTER_VIRUS) "") "true" }}
{{ if $filter_virus }}
clamav {
scan_mime_parts = false;
symbol = "CLAM_VIRUS";
type = "clamav";
action = "reject";
servers = "{{$.Env.FILTER_VIRUS_HOST}}:3310";
}
{{ end }}

View File

@ -0,0 +1 @@
autolearn = true;

View File

@ -0,0 +1,2 @@
path = "/media/dkim/$domain.$selector.key";
selector_map = "/media/dkim/dkim_selectors.map";

View File

@ -0,0 +1 @@
type = console

View File

@ -0,0 +1,11 @@
group "rbl" {
symbol "RBL_NIXSPAM_BAD" {
weight = 7.0;
description = "From address is listed in ix.dnsbl.manitu.net BL";
}
symbol "RBL_NIXSPAM" {
weight = 0.0;
description = "Unrecognised result from ix.dnsbl.manitu.net BL";
}
}

View File

@ -0,0 +1 @@
extended_spam_headers = true;

View File

@ -0,0 +1,7 @@
dns {
timeout = 1s;
sockets = 16;
retransmits = 2;
nameserver = [ "8.8.8.8:53", "8.8.4.4:53", "1.1.1.1:53", "1.0.0.1:53" ];
}

View File

@ -0,0 +1,9 @@
rbls {
nixspam {
symbol = "RBL_NIXSPAM";
rbl = "ix.dnsbl.manitu.net";
returncodes {
RBL_NIXSPAM_BAD = "127.0.0.2";
}
}
}

View File

@ -0,0 +1,5 @@
bind_socket = "*:11334";
secure_ip = "127.0.0.1";
secure_ip = "::1";
secure_ip = "172.16.0.0/12";
password = "{{$.Env.CONTROLLER_PASSWORD_ENC}}"

View File

@ -0,0 +1 @@
bind_socket = "*:11332";

View File

@ -0,0 +1,32 @@
#!/bin/sh
FILTER_VIRUS_ARGS=""
if [ ${FILTER_VIRUS} == "true" ]
then
FILTER_VIRUS_ARGS="-wait tcp://${FILTER_VIRUS_HOST}:3310"
fi
if ! [ -f /var/lib/rspamd/bayes.spam.sqlite ]
then
cp /usr/share/rspamd/bayes.spam.sqlite /var/lib/rspamd/bayes.spam.sqlite
fi
if ! [ -f /var/lib/rspamd/bayes.ham.sqlite ]
then
cp /usr/share/rspamd/bayes.ham.sqlite /var/lib/rspamd/bayes.ham.sqlite
fi
if [ "${CONTROLLER_PASSWORD}" == "changeme" ]
then
# q1 is disabled in rspamd.
export CONTROLLER_PASSWORD_ENC="q1"
else
export CONTROLLER_PASSWORD_ENC=`rspamadm pw -e -p ${CONTROLLER_PASSWORD}`
fi
dockerize \
-template /etc/rspamd/local.d/antivirus.conf.templ:/etc/rspamd/local.d/antivirus.conf \
-template /etc/rspamd/local.d/worker-controller.inc.templ:/etc/rspamd/local.d/worker-controller.inc \
${FILTER_VIRUS_ARGS} \
-timeout ${WAITSTART_TIMEOUT} \
/usr/sbin/rspamd -c /etc/rspamd/rspamd.conf -f

47
dockermail/mda/Dockerfile Normal file
View File

@ -0,0 +1,47 @@
ARG DOCKERIZE_VER=0.6.0
ARG ALPINE_VER=3.9
FROM jwilder/dockerize:${DOCKERIZE_VER} AS dockerize
FROM alpine:${ALPINE_VER}
LABEL maintainer="jeff@ressourcenkonflikt.de"
LABEL vendor="https://github.com/jeboehm/docker-mailserver"
LABEL de.ressourcenkonflikt.docker-mailserver.autoheal="true"
ENV MYSQL_HOST=db \
MYSQL_USER=root \
MYSQL_PASSWORD=changeme \
MYSQL_DATABASE=mailserver \
MAILNAME=mail.example.com \
POSTMASTER=postmaster@example.com \
SUBMISSION_HOST=mta \
ENABLE_POP3=true \
ENABLE_IMAP=true \
SSL_CERT=/media/tls/mailserver.crt \
SSL_KEY=/media/tls/mailserver.key \
WAITSTART_TIMEOUT=1m
RUN apk --no-cache add \
curl \
dovecot \
dovecot-lmtpd \
dovecot-mysql \
dovecot-pigeonhole-plugin \
dovecot-pop3d \
dovecot-submissiond && \
adduser -h /var/vmail -u 5000 -D vmail && \
rm -rf /etc/ssl/dovecot && \
openssl dhparam -out /etc/dovecot/dh.pem 2048
COPY --from=dockerize /usr/local/bin/dockerize /usr/local/bin
COPY rootfs/ /
RUN sievec /etc/dovecot/sieve/global/spam-to-folder.sieve && \
sievec /etc/dovecot/sieve/global/learn-ham.sieve && \
sievec /etc/dovecot/sieve/global/learn-spam.sieve
EXPOSE 2003 4190 143 110 993 995
VOLUME ["/var/vmail"]
HEALTHCHECK CMD echo "? LOGOUT" | nc 127.0.0.1 143 | grep "Dovecot ready."
CMD ["/usr/local/bin/entrypoint.sh"]

View File

@ -0,0 +1,3 @@
auth_mechanisms = plain login
!include auth-sql.conf.ext

View File

@ -0,0 +1,2 @@
log_path = /dev/stderr
info_log_path = /dev/stdout

View File

@ -0,0 +1,10 @@
mail_location = maildir:/var/vmail/%d/%n/Maildir
mail_home = /var/vmail/%d/%n
mail_uid = vmail
mail_gid = vmail
mail_privileged_group = vmail
mail_plugins = $mail_plugins quota
namespace inbox {
inbox = yes
}

View File

@ -0,0 +1,56 @@
protocols = lmtp submission
{{ $enable_pop3 := eq (or ($.Env.ENABLE_POP3) "") "true" }}
{{ $enable_imap := eq (or ($.Env.ENABLE_IMAP) "") "true" }}
{{ if $enable_imap }}
service imap-login {
inet_listener imap {
#port = 143
}
inet_listener imaps {
#port = 993
#ssl = yes
}
}
service imap {
}
protocols = $protocols imap
{{ end }}
{{ if $enable_pop3 }}
service pop3-login {
inet_listener pop3 {
#port = 110
}
inet_listener pop3s {
#port = 995
#ssl = yes
}
}
service pop3 {
}
protocols = $protocols pop3
{{ end }}
service submission-login {
inet_listener submission {
#port = 587
}
}
service submission {
#process_limit = 1024
}
service lmtp {
inet_listener lmtp {
port = 2003
}
}

View File

@ -0,0 +1,4 @@
ssl = yes
ssl_cert = </media/tls/mailserver.crt
ssl_key = </media/tls/mailserver.key
ssl_dh = </etc/dovecot/dh.pem

View File

@ -0,0 +1,5 @@
postmaster_address = {{ .Env.POSTMASTER }}
hostname = {{ .Env.MAILNAME }}
submission_host = {{ .Env.SUBMISSION_HOST }}
lda_mailbox_autosubscribe = yes
recipient_delimiter = -

View File

@ -0,0 +1,24 @@
namespace inbox {
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = subscribe
special_use = \Junk
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
mailbox Sent {
special_use = \Sent
}
mailbox "Sent Messages" {
special_use = \Sent
}
}

View File

@ -0,0 +1,3 @@
protocol imap {
mail_plugins = $mail_plugins imap_sieve imap_quota
}

View File

@ -0,0 +1,3 @@
protocol lmtp {
mail_plugins = $mail_plugins sieve
}

View File

@ -0,0 +1,7 @@
protocols = $protocols sieve
service managesieve-login {
inet_listener sieve {
port = 4190
}
}

View File

@ -0,0 +1,6 @@
submission_relay_host = {{ .Env.SUBMISSION_HOST }}
submission_relay_port = 25
submission_relay_trusted = yes
protocol submission {
}

View File

@ -0,0 +1,4 @@
plugin {
quota = maildir:User quota
quota_exceeded_message = "Benutzer %u hat das Speichervolumen überschritten. / User %u has exhausted allowed storage space."
}

View File

@ -0,0 +1,20 @@
plugin {
sieve_plugins = sieve_imapsieve sieve_extprograms
sieve_global_extensions = +vnd.dovecot.pipe
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_before = /etc/dovecot/sieve/global/spam-to-folder.sieve
sieve_pipe_bin_dir = /usr/lib/dovecot/sieve
sieve_pipe_exec_timeout = 60s
recipient_delimiter = -
# From somewhere to junk folder
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:/etc/dovecot/sieve/global/learn-spam.sieve
# From junk folder to elsewhere
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/etc/dovecot/sieve/global/learn-ham.sieve
}

View File

@ -0,0 +1,9 @@
passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}

View File

@ -0,0 +1,6 @@
driver = mysql
connect = host={{ .Env.MYSQL_HOST }} dbname={{ .Env.MYSQL_DATABASE }} user={{ .Env.MYSQL_USER }} password={{ .Env.MYSQL_PASSWORD }}
default_pass_scheme = SHA256-CRYPT
password_query = SELECT mail_users.name AS user, mail_domains.name AS domain, password FROM mail_users JOIN mail_domains ON mail_users.domain_id = mail_domains.id WHERE mail_users.name = '%n' AND mail_domains.name = '%d' AND enabled = 1 AND NOT (send_only = 1 AND "%s" in ('imap', 'pop3'));
user_query = SELECT concat('*:storage=', quota, 'M') AS quota_rule FROM mail_users JOIN mail_domains ON mail_users.domain_id = mail_domains.id WHERE mail_users.name = '%n' AND mail_domains.name = '%d';
iterate_query = SELECT mail_users.name AS username, mail_domains.name AS domain FROM mail_users JOIN mail_domains ON mail_users.domain_id = mail_domains.id;

View File

@ -0,0 +1,2 @@
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamc" ["learnham"];

View File

@ -0,0 +1,2 @@
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamc" ["learnspam"];

View File

@ -0,0 +1,6 @@
require ["fileinto","mailbox"];
if header :contains "X-Spam" "YES" {
fileinto :create "Junk";
stop;
}

View File

@ -0,0 +1,5 @@
#!/bin/sh
ACTION=${1}
curl -q -s --connect-timeout 60 --data-binary @- http://filter:11334/${ACTION}

View File

@ -0,0 +1,11 @@
#!/bin/sh
set -e
openssl dhparam -out /etc/dovecot/dh.pem.tmp 4096
mv /etc/dovecot/dh.pem.tmp /etc/dovecot/dh.pem
touch /etc/dovecot/dh.pem.created
echo "The Diffie Hellman file was generated."
echo "YOU SHOULD RESTART THIS CONTAINER NOW."
exit 0

View File

@ -0,0 +1,20 @@
#!/bin/sh
if ! [ -r /etc/dovecot/dh.pem.created ]
then
echo "Using pre-generated Diffie Hellman parameters until the new one is generated."
/usr/local/bin/dh.sh &
fi
rm -f /run/dovecot/master.pid
dockerize \
-template /etc/dovecot/conf.d/10-master.conf.templ:/etc/dovecot/conf.d/10-master.conf \
-template /etc/dovecot/conf.d/15-lda.conf.templ:/etc/dovecot/conf.d/15-lda.conf \
-template /etc/dovecot/conf.d/20-submission.conf.templ:/etc/dovecot/conf.d/20-submission.conf \
-template /etc/dovecot/dovecot-sql.conf.ext.templ:/etc/dovecot/dovecot-sql.conf.ext \
-wait tcp://${MYSQL_HOST}:3306 \
-wait file://${SSL_CERT} \
-wait file://${SSL_KEY} \
-timeout ${WAITSTART_TIMEOUT} \
/usr/sbin/dovecot -F

66
dockermail/mta/Dockerfile Normal file
View File

@ -0,0 +1,66 @@
ARG DOCKERIZE_VER=0.6.0
ARG ALPINE_VER=3.9
FROM jwilder/dockerize:${DOCKERIZE_VER} AS dockerize
FROM alpine:${ALPINE_VER}
LABEL maintainer="jeff@ressourcenkonflikt.de"
LABEL vendor="https://github.com/jeboehm/docker-mailserver"
LABEL de.ressourcenkonflikt.docker-mailserver.autoheal="true"
ENV MAILNAME=mail.example.com \
MYNETWORKS=127.0.0.0/8\ 10.0.0.0/8\ 172.16.0.0/12\ 192.168.0.0/16 \
MYSQL_HOST=db \
MYSQL_USER=root \
MYSQL_PASSWORD=changeme \
MYSQL_DATABASE=mailserver \
FILTER_MIME=false \
RSPAMD_HOST=filter \
MDA_HOST=mda \
MTA_HOST=mta \
RELAYHOST=false \
SSL_CERT=/media/tls/mailserver.crt \
SSL_KEY=/media/tls/mailserver.key \
WAITSTART_TIMEOUT=1m
RUN apk --no-cache add \
postfix-mysql \
postfix \
supervisor && \
postconf virtual_mailbox_domains=mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf && \
postconf virtual_mailbox_maps=mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf && \
postconf virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf,mysql:/etc/postfix/mysql-email2email.cf && \
postconf smtpd_recipient_restrictions="check_recipient_access mysql:/etc/postfix/mysql-recipient-access.cf" && \
postconf smtputf8_enable=no && \
postconf smtpd_milters="inet:${RSPAMD_HOST}:11332" && \
postconf non_smtpd_milters="inet:${RSPAMD_HOST}:11332" && \
postconf milter_protocol=6 && \
postconf milter_mail_macros="i {mail_addr} {client_addr} {client_name} {auth_authen}" && \
postconf milter_default_action=accept && \
postconf virtual_transport="lmtp:${MDA_HOST}:2003" && \
postconf smtp_tls_security_level=may && \
postconf smtpd_tls_security_level=may && \
postconf smtpd_tls_auth_only=yes && \
postconf smtpd_tls_cert_file="${SSL_CERT}" && \
postconf smtpd_tls_key_file="${SSL_KEY}" && \
postconf smtpd_discard_ehlo_keywords="silent-discard, dsn" && \
postconf soft_bounce=no && \
postconf message_size_limit=52428800 && \
postconf mailbox_size_limit=0 && \
postconf recipient_delimiter=- && \
postconf mynetworks="$MYNETWORKS" && \
postconf maximal_queue_lifetime=1h && \
postconf bounce_queue_lifetime=1h && \
postconf maximal_backoff_time=15m && \
postconf minimal_backoff_time=5m && \
postconf queue_run_delay=5m && \
newaliases
COPY --from=dockerize /usr/local/bin/dockerize /usr/local/bin
COPY rootfs/ /
EXPOSE 25
VOLUME ["/var/spool/postfix"]
HEALTHCHECK CMD postfix status || exit 1
CMD ["/usr/local/bin/entrypoint.sh"]

View File

@ -0,0 +1 @@
/name=[^>]*\.(bat|com|exe|dll|vbs|docm|doc|dzip)/ REJECT

View File

@ -0,0 +1,5 @@
user = {{ .Env.MYSQL_USER }}
password = {{ .Env.MYSQL_PASSWORD }}
hosts = {{ .Env.MYSQL_HOST }}
dbname = {{ .Env.MYSQL_DATABASE }}
query = SELECT CONCAT(mail_users.name, '@', mail_domains.name) AS email FROM mail_users JOIN mail_domains ON mail_users.domain_id = mail_domains.id HAVING email='%s'

View File

@ -0,0 +1,5 @@
user = {{ .Env.MYSQL_USER }}
password = {{ .Env.MYSQL_PASSWORD }}
hosts = {{ .Env.MYSQL_HOST }}
dbname = {{ .Env.MYSQL_DATABASE }}
query = SELECT IF(send_only = true, 'REJECT', 'OK') AS access FROM mail_users JOIN mail_domains ON mail_users.domain_id = mail_domains.id WHERE mail_users.name = '%u' AND mail_domains.name = '%d'

View File

@ -0,0 +1,5 @@
user = {{ .Env.MYSQL_USER }}
password = {{ .Env.MYSQL_PASSWORD }}
hosts = {{ .Env.MYSQL_HOST }}
dbname = {{ .Env.MYSQL_DATABASE }}
query = SELECT destination FROM mail_aliases JOIN mail_domains ON mail_aliases.domain_id = mail_domains.id WHERE CONCAT(mail_aliases.name, '@', mail_domains.name) = '%s'

View File

@ -0,0 +1,5 @@
user = {{ .Env.MYSQL_USER }}
password = {{ .Env.MYSQL_PASSWORD }}
hosts = {{ .Env.MYSQL_HOST }}
dbname = {{ .Env.MYSQL_DATABASE }}
query = SELECT 1 FROM mail_domains WHERE name='%s'

View File

@ -0,0 +1,5 @@
user = {{ .Env.MYSQL_USER }}
password = {{ .Env.MYSQL_PASSWORD }}
hosts = {{ .Env.MYSQL_HOST }}
dbname = {{ .Env.MYSQL_DATABASE }}
query = SELECT 1 FROM mail_users JOIN mail_domains ON mail_users.domain_id = mail_domains.id WHERE mail_users.name = '%u' AND mail_domains.name = '%d' AND enabled = 1

View File

@ -0,0 +1,18 @@
[supervisord]
nodaemon=true
logfile=/dev/stderr
logfile_maxbytes=0
pidfile=/tmp/supervisord.pid
user=root
[program:syslogd]
command=/bin/busybox syslogd -n -O - -S
redirect_stderr=true
stdout_logfile=/dev/stderr
stdout_logfile_maxbytes=0
[program:postfix]
command=/usr/libexec/postfix/master -d
redirect_stderr=true
stdout_logfile=/dev/stderr
stdout_logfile_maxbytes=0

View File

@ -0,0 +1,29 @@
#!/bin/sh
set -e
postconf myhostname="${MAILNAME}"
postconf mynetworks="${MYNETWORKS}"
if [ "${FILTER_MIME}" == "true" ]
then
postconf mime_header_checks=regexp:/etc/postfix/mime_header_checks
fi
if [ "${RELAYHOST}" != "false" ]
then
postconf relayhost=${RELAYHOST}
fi
dockerize \
-template /etc/postfix/mysql-email2email.cf.templ:/etc/postfix/mysql-email2email.cf \
-template /etc/postfix/mysql-virtual-alias-maps.cf.templ:/etc/postfix/mysql-virtual-alias-maps.cf \
-template /etc/postfix/mysql-virtual-mailbox-domains.cf.templ:/etc/postfix/mysql-virtual-mailbox-domains.cf \
-template /etc/postfix/mysql-virtual-mailbox-maps.cf.templ:/etc/postfix/mysql-virtual-mailbox-maps.cf \
-template /etc/postfix/mysql-recipient-access.cf.templ:/etc/postfix/mysql-recipient-access.cf \
-wait tcp://${MYSQL_HOST}:3306 \
-wait tcp://${MDA_HOST}:2003 \
-wait tcp://${RSPAMD_HOST}:11332 \
-wait file://${SSL_CERT} \
-wait file://${SSL_KEY} \
-timeout ${WAITSTART_TIMEOUT} \
/usr/bin/supervisord

View File

@ -1,17 +0,0 @@
#!/bin/bash
if ! command -v swaks &> /dev/null
then
echo "SWAKS not found."
exit
fi
read -p "TO: " to_input
read -p "FROM: " from_input
read -s -p "PASS: " pass_input
printf "\n"
read -p "SERVER: " host_input
read -p "HEADER: " header_input
read -p "BODY: " body_input
swaks -t "$to_input" -f "$from_input" -s "$host_input" -au "$from_input" -ap "$pass_input" --header "$header_input" --body "$body_input" -tlso

View File

@ -1,23 +0,0 @@
#!/bin/bash
mkdir ./mailserver
pushd ./mailserver || exit
curl -o setup.sh https://raw.githubusercontent.com/tomav/docker-mailserver/master/setup.sh && chmod a+x ./setup.sh
curl -o docker-compose.yml https://raw.githubusercontent.com/tomav/docker-mailserver/master/docker-compose.yml.dist
curl -o env-mailserver https://raw.githubusercontent.com/tomav/docker-mailserver/master/env-mailserver.dist
if [ -f .env ]; then
rm ./.env
fi
echo "CONTAINER_NAME=mail" >> .env
read -r -p "HOSTNAME: "
echo "HOSTNAME=$REPLY" >> .env
read -r -p "DOMAIN: "
echo "DOMAINNAME=$REPLY" >> .env
printf "\nSetup the first account.\n"
read -r -p "Enter Email: " user
bash ./setup.sh email add "$user"
bash ./setup.sh config dkim

20
dockermail/ssl/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
ARG ALPINE_VER=3.9
FROM alpine:${ALPINE_VER}
LABEL maintainer="jeff@ressourcenkonflikt.de"
LABEL vendor="https://github.com/jeboehm/docker-mailserver"
ENV SSL_CERT=/media/tls/mailserver.crt \
SSL_KEY=/media/tls/mailserver.key \
SSL_CSR=/media/tls/mailserver.csr \
SSL_SUBJ_COUNTRY=DE \
SSL_SUBJ_STATE=Northrhine-Westfalia \
SSL_SUBJ_LOCALITY=Duesseldorf \
SSL_SUBJ_ORGANIZATION=Mail \
SSL_SUBJ_ORGANIZATIONAL_UNIT=Mail
RUN apk --no-cache add openssl
COPY create_tls.sh /usr/local/bin
CMD ["/usr/local/bin/create_tls.sh"]

View File

@ -0,0 +1,17 @@
#!/bin/sh
set -e
if [ -r ${SSL_CERT} ]
then
echo "SSL certificate found. Exiting..."
exit 0
fi
echo "No SSL certificate found. Creating a new one..."
openssl req -nodes -newkey rsa:2048 -keyout ${SSL_KEY} -out ${SSL_CSR} -subj "/C=${SSL_SUBJ_COUNTRY}/ST=${SSL_SUBJ_STATE}/L=${SSL_SUBJ_LOCALITY}/O=${SSL_SUBJ_ORGANIZATION}/OU=${SSL_SUBJ_ORGANIZATIONAL_UNIT}/CN=${MAILNAME}"
openssl x509 -req -days 3000 -in ${SSL_CSR} -signkey ${SSL_KEY} -out ${SSL_CERT}
echo "SSL certificate was successfully created! Exiting..."
exit 0

View File

@ -0,0 +1,46 @@
ARG DOCKERIZE_VER=0.6.0
ARG ALPINE_VER=3.9
FROM jwilder/dockerize:${DOCKERIZE_VER} AS dockerize
FROM alpine:${ALPINE_VER}
LABEL maintainer="jeff@ressourcenkonflikt.de"
LABEL vendor="https://github.com/jeboehm/docker-mailserver"
ENV MYSQL_HOST=db \
MYSQL_USER=root \
MYSQL_PASSWORD=changeme \
MYSQL_DATABASE=mailserver \
WAITSTART_TIMEOUT=1m
# Iconv fix: https://github.com/docker-library/php/issues/240#issuecomment-305038173
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ gnu-libiconv
ENV LD_PRELOAD=/usr/lib/preloadable_libiconv.so
RUN apk --no-cache add \
bash \
bats \
curl \
docker \
jq \
mariadb-client \
openssl \
perl \
perl-net-ssleay \
php7 \
php7-imap \
php7-phar \
php7-iconv \
php7-openssl \
&& wget -q -O /usr/local/bin/swaks https://www.jetmore.org/john/code/swaks/files/swaks-20130209.0/swaks \
&& chmod +x /usr/local/bin/swaks \
&& wget -q -O /usr/local/bin/imap-tester https://github.com/jeboehm/imap-tester/releases/download/v0.2.1/imap-tester.phar \
&& chmod +x /usr/local/bin/imap-tester \
&& mkdir -p /usr/share/fixtures \
&& wget -q -O /usr/share/fixtures/gtube.txt https://spamassassin.apache.org/gtube/gtube.txt \
&& wget -q -O /usr/share/fixtures/eicar.com https://secure.eicar.org/eicar.com
COPY --from=dockerize /usr/local/bin/dockerize /usr/local/bin
COPY rootfs/ /
CMD ["/usr/local/bin/run-tests.sh"]

View File

@ -0,0 +1,10 @@
#!/bin/sh
dockerize \
-wait tcp://db:3306 \
-wait tcp://mta:25 \
-wait tcp://web:80 \
-wait tcp://mda:143 \
-wait tcp://filter:11334 \
-timeout ${WAITSTART_TIMEOUT} \
bats /usr/share/tests/*.bats

View File

@ -0,0 +1,30 @@
#!/usr/bin/env bats
@test "certificates were created" {
[ -f /media/tls/mailserver.crt ]
}
@test "connection to imaps" {
true | openssl s_client -showcerts -connect mda:993
[ "$?" -eq 0 ]
}
@test "connection to pop3s" {
true | openssl s_client -showcerts -connect mda:995
[ "$?" -eq 0 ]
}
@test "connection to pop3 with starttls" {
true | openssl s_client -showcerts -connect mda:110 -starttls pop3
[ "$?" -eq 0 ]
}
@test "connection to imap with starttls" {
true | openssl s_client -showcerts -connect mda:143 -starttls imap
[ "$?" -eq 0 ]
}
@test "connection to smtp with starttls" {
true | openssl s_client -showcerts -connect mta:25 -starttls smtp
[ "$?" -eq 0 ]
}

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bats
@test "user table exists" {
run mysql --batch -u "${MYSQL_USER}" --password="${MYSQL_PASSWORD}" -h "${MYSQL_HOST}" "${MYSQL_DATABASE}" -e "select * from mail_users;"
[ "$status" = 0 ]
}
@test "alias table exists" {
run mysql --batch -u "${MYSQL_USER}" --password="${MYSQL_PASSWORD}" -h "${MYSQL_HOST}" "${MYSQL_DATABASE}" -e "select * from mail_aliases;"
[ "$status" = 0 ]
}
@test "domain table exists" {
run mysql --batch -u "${MYSQL_USER}" --password="${MYSQL_PASSWORD}" -h "${MYSQL_HOST}" "${MYSQL_DATABASE}" -e "select * from mail_domains;"
[ "$status" = 0 ]
}

View File

@ -0,0 +1,131 @@
#!/usr/bin/env bats
@test "send mail to local account address" {
run swaks -s mta --to admin@example.com --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 0 ]
}
@test "send mail to local address with extension" {
run swaks -s mta --to admin-test@example.com --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 0 ]
}
@test "authentification on smtp with disabled account should fail" {
run swaks -s mta --to admin@example.com --from disabled@example.com -a -au disabled@example.com -ap test1234 -tls --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 28 ]
}
@test "authentification on smtp with disabled and send only account should fail" {
run swaks -s mta --to admin@example.com --from disabledsendonly@example.com -a -au disabled@example.com -ap test1234 -tls --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 28 ]
}
@test "send mail to mda with smtp authentification (submission service)" {
run swaks -s mda --port 587 --to admin@example.com --from admin@example.com -a -au admin@example.com -ap changeme -tls --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 0 ]
}
@test "send mail to mda with smtp authentification, with address extension (submission service)" {
run swaks -s mda --port 587 --to admin@example.com --from admin-extension@example.com -a -au admin@example.com -ap changeme -tls --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 0 ]
}
@test "send mail to mda from sendonly account with smtp authentification (submission service)" {
run swaks -s mda --port 587 --to admin@example.com --from sendonly@example.com -a -au sendonly@example.com -ap test1234 -tls --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 0 ]
}
@test "send mail to local alias" {
run swaks -s mta --to foo@example.com --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 0 ]
}
@test "send junk mail to local address" {
run swaks -s mta --to admin@example.com --body "$BATS_TEST_DESCRIPTION" --header "X-Spam: Yes"
[ "$status" -eq 0 ]
}
@test "send mail with too big attachment to quota user" {
dd if=/dev/urandom of=/tmp/bigfile bs=1M count=5
run swaks -s mta --to quota@example.com --body "$BATS_TEST_DESCRIPTION" --attach /tmp/bigfile
[ "$status" -eq 0 ]
}
@test "send mail to disabled user" {
run swaks -s mta --to disabled@example.com --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 0 ]
}
@test "maildir was created" {
sleep 10 # MTA + MDA need some time. :)
[ -d /var/vmail/example.com/admin/Maildir/new/ ]
}
@test "mail to local account address is stored" {
run grep -r "send mail to local account address" /var/vmail/example.com/admin/Maildir/
[ "$status" -eq 0 ]
}
@test "mail to local alias is stored" {
run grep -r "send mail to local alias" /var/vmail/example.com/admin/Maildir/
[ "$status" -eq 0 ]
}
@test "mail to local address with extension is stored" {
run grep -r "send mail to local address with extension" /var/vmail/example.com/admin/Maildir/
[ "$status" -eq 0 ]
}
@test "mail to mda with smtp authentification (submission service) is stored" {
run grep -r "send mail to mda with smtp authentification (submission service)" /var/vmail/example.com/admin/Maildir/
[ "$status" -eq 0 ]
}
@test "send mail to mda with smtp authentification, with address extension (submission service) is stored" {
run grep -r "send mail to mda with smtp authentification, with address extension (submission service)" /var/vmail/example.com/admin/Maildir/
[ "$status" -eq 0 ]
}
@test "send mail to mda from sendonly account with smtp authentification (submission service) is stored" {
run grep -r "send mail to mda from sendonly account with smtp authentification (submission service)" /var/vmail/example.com/admin/Maildir/
[ "$status" -eq 0 ]
}
@test "junk mail is assorted to the junk folder" {
run grep -r "send junk mail to local address" /var/vmail/example.com/admin/Maildir/.Junk/
[ "$status" -eq 0 ]
}
@test "mail with too big attachment is not found" {
run grep -r "send mail with too big attachment to quota user" /var/vmail/example.com/quota/Maildir/
[ "$status" -ne 0 ]
}
@test "mail to disabled user is stored anyway" {
run grep -r "send mail to disabled user" /var/vmail/example.com/disabled/Maildir/
[ "$status" -eq 0 ]
}
@test "send gtube mail is rejected" {
run swaks -s mta --to admin@example.com --data /usr/share/fixtures/gtube.txt
[ "$status" -eq 26 ]
}
@test "mail to send only mailbox is rejected" {
run swaks -s mta --to sendonly@example.com --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 24 ]
}
@test "mail to disabled and send only mailbox is rejected anyway" {
run swaks -s mta --to disabledsendonly@example.com --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 24 ]
}
@test "virus is rejected" {
if [ ${FILTER_VIRUS} = "false" ]; then
skip
fi
run swaks -s mta --to admin@example.com --attach - < /usr/share/fixtures/eicar.com
[ "$status" -eq 26 ]
}

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bats
@test "http connection to manager web interface" {
curl -L http://web/manager/ | grep "Email address"
[ "$?" -eq 0 ]
}
@test "http connection to webmail interface" {
curl http://web/webmail/ | grep "jeboehm"
[ "$?" -eq 0 ]
}
@test "http connection to rspamd interface" {
curl http://web/rspamd/ | grep "Rspamd Web Interface"
[ "$?" -eq 0 ]
}

View File

@ -0,0 +1,72 @@
#!/usr/bin/env bats
@test "send mail to mda from disabled account with smtp authentification (submission service)" {
run swaks -s mda --port 587 --to admin@example.com --from disabled@example.com -a -au disabled@example.com -ap test1234 -tls --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 28 ]
}
@test "send mail to mda without authentification (submission service)" {
run swaks -s mda --port 587 --to admin@example.com --from disabled@example.com -tls --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 23 ]
}
@test "send mail to mda without tls (submission service)" {
run swaks -s mda --port 587 --to admin@example.com --from admin@example.com -a -au admin@example.com -ap changeme --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 28 ]
}
@test "count mails in inbox via imap" {
run imap-tester test:count mda 143 admin@example.com changeme INBOX
[ "$output" -gt 3 ]
}
@test "count mails in inbox via imaps" {
run imap-tester test:count mda 993 admin@example.com changeme INBOX
[ "$output" -gt 3 ]
}
@test "count mails in inbox via pop3" {
run imap-tester test:count mda 110 admin@example.com changeme INBOX
[ "$output" -gt 3 ]
}
@test "count mails in inbox via pop3s" {
run imap-tester test:count mda 995 admin@example.com changeme INBOX
[ "$output" -gt 3 ]
}
@test "imap login to send only mailbox is not possible" {
run imap-tester test:count mda 143 sendonly@example.com test1234 INBOX
[ "$status" -eq 1 ]
}
@test "pop3 login to send only mailbox is not possible" {
run imap-tester test:count mda 110 sendonly@example.com test1234 INBOX
[ "$status" -eq 1 ]
}
@test "pop3 login to quota mailbox is possible" {
run imap-tester test:count mda 110 quota@example.com test1234 INBOX
[ "$status" -eq 0 ]
}
@test "imap login to quota mailbox is possible" {
run imap-tester test:count mda 143 quota@example.com test1234 INBOX
[ "$status" -eq 0 ]
}
@test "pop3 login to disabled mailbox is not possible" {
run imap-tester test:count mda 110 disabled@example.com test1234 INBOX
[ "$status" -eq 1 ]
}
@test "imap login to disabled mailbox is not possible" {
run imap-tester test:count mda 143 disabled@example.com test1234 INBOX
[ "$status" -eq 1 ]
}
@test "mails are owned by vmail" {
run find /var/vmail/example.com/ -not -user 5000
[ "$status" -eq 0 ]
[ "$output" = "" ]
}

View File

@ -0,0 +1,18 @@
#!/usr/bin/env bats
@test "no unhealthy containers exist" {
run docker ps -q --filter health=unhealthy
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@test "Virus container is not running when filtering is disabled" {
if [ ${FILTER_VIRUS} = "true" ]; then
echo '# Filtering is disabled, skipping test' >&3
skip
fi
run docker ps -q --filter name=docker-mailserver_virus_1
[ "$status" -eq 0 ]
[ "$output" = "" ]
}

View File

@ -0,0 +1,34 @@
#!/usr/bin/env bats
@test "check mailhog api for messages" {
if [ ${RELAYHOST} = "false" ]; then
echo '# Relayhost is disabled, skipping test' >&3
skip
fi
run curl "http://mailhog:8025/api/v2/messages"
[ "$status" -eq 0 ]
}
@test "send mail to mda with smtp authentification, external recipient" {
if [ ${RELAYHOST} = "false" ]; then
echo '# Relayhost is disabled, skipping test' >&3
skip
fi
run swaks -s mda --port 587 --to nobody@ressourcenkonflikt.de --from admin@example.com -a -au admin@example.com -ap changeme -tls --body "$BATS_TEST_DESCRIPTION"
[ "$status" -eq 0 ]
}
@test "check mailhog api for outgoing message" {
if [ ${RELAYHOST} = "false" ]; then
echo '# Relayhost is disabled, skipping test' >&3
skip
fi
sleep 5 # Give mailhog some time
RESULT=$(curl -s "http://mailhog:8025/api/v2/messages" | jq -cr .items[0].Content.Body | tr -d '[:space:]')
[ "$RESULT" = "sendmailtomdawithsmtpauthentification,externalrecipient" ]
}

View File

@ -0,0 +1,9 @@
#!/usr/bin/env bats
@test "check DKIM selector map exists" {
[ -r /media/dkim/dkim_selectors.map ]
}
@test "check DKIM key for example.com exists" {
[ -r /media/dkim/example.com.1337.key ]
}

View File

@ -1,6 +0,0 @@
#|/bin/bash
pushd ./mailserver || exit
docker-compose down
docker pull tvial/docker-mailserver:latest
docker-compose up -d mail

View File

@ -0,0 +1,26 @@
ARG ALPINE_VER=3.11
FROM alpine:${ALPINE_VER}
LABEL maintainer="jeff@ressourcenkonflikt.de"
LABEL vendor="https://github.com/jeboehm/docker-mailserver"
LABEL de.ressourcenkonflikt.docker-mailserver.autoheal="true"
ENV FILTER_VIRUS=true
RUN apk --no-cache add \
clamav-daemon \
clamav-libunrar && \
rm -rf /var/log/clamav
COPY rootfs/ /
EXPOSE 3310
USER clamav
RUN /usr/bin/freshclam -l /dev/null
VOLUME ["/var/lib/clamav"]
HEALTHCHECK CMD echo PING | nc 127.0.0.1 3310 | grep PONG
CMD ["/usr/local/bin/entrypoint.sh"]

View File

@ -0,0 +1,35 @@
ARG ALPINE_VER=3.9
FROM alpine:${ALPINE_VER}
LABEL maintainer="jeff@ressourcenkonflikt.de"
LABEL vendor="https://github.com/jeboehm/docker-mailserver"
# hadolint ignore=DL3003
RUN apk --no-cache add \
bash \
bind-tools \
clamav-scanner \
gnupg \
ncurses \
rsync \
wget && \
wget -q -O /tmp/master.tar.gz https://github.com/extremeshok/clamav-unofficial-sigs/archive/master.tar.gz && \
cd /tmp && \
tar -xvf master.tar.gz && \
cd clamav-unofficial-sigs-master && \
cp clamav-unofficial-sigs.sh /usr/local/bin/ && \
chmod +x /usr/local/bin/clamav-unofficial-sigs.sh && \
cp -r config /etc/clamav-unofficial-sigs && \
mkdir /var/lib/clamav-unofficial-sigs && \
chown clamav /var/lib/clamav-unofficial-sigs && \
cp /etc/clamav-unofficial-sigs/os/os.ubuntu.conf /etc/clamav-unofficial-sigs/os.conf && \
echo "user_configuration_complete=\"yes\"" >> /etc/clamav-unofficial-sigs/user.conf && \
echo "logging_enabled=\"no\"" >> /etc/clamav-unofficial-sigs/user.conf && \
echo "enable_random=\"no\"" >> /etc/clamav-unofficial-sigs/user.conf && \
echo "reload_dbs=\"no\"" >> /etc/clamav-unofficial-sigs/user.conf && \
rm -rf /tmp/* /var/log/* /etc/clamav-unofficial-sigs/os/
USER clamav
CMD ["/usr/local/bin/clamav-unofficial-sigs.sh"]

View File

@ -0,0 +1,2 @@
TCPSocket 3310
Foreground true

View File

@ -0,0 +1,4 @@
DatabaseOwner clamav
DatabaseMirror database.clamav.net
ScriptedUpdates yes
NotifyClamd /etc/clamav/clamd.conf

View File

@ -0,0 +1,10 @@
#!/bin/sh
if [ "${FILTER_VIRUS}" = "false" ]
then
echo "Virus filtering is disabled, exiting."
exit 0
fi
/usr/bin/freshclam -d -l /dev/stdout &
/usr/sbin/clamd

50
dockermail/web/Dockerfile Normal file
View File

@ -0,0 +1,50 @@
ARG ROUNDCUBE_VER=1.4.x-fpm
ARG PHP_VER=7.4
ARG DOCKERIZE_VER=0.6.0
FROM jwilder/dockerize:${DOCKERIZE_VER} AS dockerize
FROM roundcube/roundcubemail:${ROUNDCUBE_VER} AS roundcube
FROM jeboehm/php-nginx-base:${PHP_VER}
ARG ADMIN_VER=1.6.1
LABEL maintainer="jeff@ressourcenkonflikt.de"
LABEL vendor="https://github.com/jeboehm/docker-mailserver"
LABEL de.ressourcenkonflikt.docker-mailserver.autoheal="true"
ENV MYSQL_HOST=db \
MYSQL_DATABASE=mailserver \
MYSQL_USER=mailserver \
MYSQL_PASSWORD=changeme \
MTA_HOST=mta \
MDA_HOST=mda \
FILTER_HOST=filter \
SUPPORT_URL=https://github.com/jeboehm/docker-mailserver \
APP_ENV=prod \
TRUSTED_PROXIES=172.16.0.0/12 \
WAITSTART_TIMEOUT=1m \
ADMIN_VER=${ADMIN_VER}
COPY --from=roundcube /usr/src/roundcubemail/ /var/www/html/webmail/
COPY --from=dockerize /usr/local/bin/dockerize /usr/local/bin
COPY rootfs/ /
WORKDIR /opt/manager
RUN wget -O /tmp/admin.tar.gz -q https://github.com/jeboehm/mailserver-admin/archive/${ADMIN_VER}.tar.gz && \
tar -xf /tmp/admin.tar.gz -C /opt/manager --strip=1 && \
rm /tmp/admin.tar.gz && \
composer install --no-dev -o
RUN ln -s /opt/manager/public /var/www/html/manager && \
chown -R www-data \
/opt/manager/var/cache/ \
/opt/manager/var/log/ \
/var/www/html/webmail/temp/ \
/var/www/html/webmail/logs/
WORKDIR /var/www/html
HEALTHCHECK CMD curl -s http://127.0.0.1/login | grep docker-mailserver
CMD ["/usr/local/bin/entrypoint.sh"]

6
dockermail/web/README.md Normal file
View File

@ -0,0 +1,6 @@
# mailserver-web
This image contains
[roundcube](https://roundcube.net)
and [mailserver-admin](https://github.com/jeboehm/mailserver-admin).

View File

@ -0,0 +1,68 @@
server {
listen 80;
absolute_redirect off;
root /var/www/html/manager;
location = /favicon.ico {
log_not_found off;
access_log off;
}
location /manager {
return 301 /;
}
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ \.(tpl|yml|ini|log)$ {
deny all;
}
location / {
try_files $uri /index.php$is_args$args;
}
location /webmail {
alias /var/www/html/webmail;
index index.php;
try_files $uri $uri/ @webmail;
location ~ \.php$ {
include fastcgi_params;
# Mitigate httpoxy vulnerability, see: https://httpoxy.org/
fastcgi_param HTTP_PROXY "";
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
client_max_body_size 24M;
client_body_buffer_size 128k;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_pass php-fpm;
}
}
location @webmail {
rewrite /webmail/(.*)$ /webmail/index.php?/$1 last;
}
location ~ ^/index\.php(/|$) {
fastcgi_pass php-fpm;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
internal;
}
location /rspamd/ {
proxy_pass http://filter:11334/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View File

@ -0,0 +1,42 @@
#!/bin/sh
set -e
manager_init() {
cd /opt/manager
bin/console doctrine:migrations:migrate -n
bin/console doctrine:schema:update --force
}
roundcube_init() {
cd /var/www/html/webmail
PWD=`pwd`
bin/initdb.sh --dir=$PWD/SQL || bin/updatedb.sh --dir=$PWD/SQL --package=roundcube || echo "Failed to initialize databse. Please run $PWD/bin/initdb.sh manually."
}
permissions() {
chown -R www-data /media/dkim
chmod 777 /media/dkim
}
dkim_refresh() {
cd /opt/manager
bin/console dkim:refresh
}
dockerize \
-wait tcp://${MYSQL_HOST}:3306 \
-wait tcp://${MDA_HOST}:143 \
-wait tcp://${MTA_HOST}:25 \
-wait tcp://${FILTER_HOST}:11334 \
-wait file:///media/dkim/ \
-timeout ${WAITSTART_TIMEOUT}
manager_init
roundcube_init
permissions
dkim_refresh
/usr/bin/supervisord

View File

@ -0,0 +1,7 @@
#!/bin/sh
dockerize \
-wait tcp://web:80 \
-wait tcp://${MYSQL_HOST}:3306 \
-timeout ${WAITSTART_TIMEOUT} \
${@}

View File

@ -0,0 +1,4 @@
#!/bin/sh
/usr/local/bin/fixtures.sh \
/opt/manager/bin/console init:setup

View File

@ -0,0 +1,39 @@
<?php
$config = [];
$config['smtp_log'] = false;
$config['log_dir'] = '/tmp/';
$config['temp_dir'] = '/tmp/';
$config['imap_cache'] = 'apc';
$config['db_dsnw'] = sprintf(
'mysql://%s:%s@%s/%s',
getenv('MYSQL_USER'),
getenv('MYSQL_PASSWORD'),
getenv('MYSQL_HOST'),
getenv('MYSQL_DATABASE')
);
$config['default_host'] = 'tls://' . getenv('MDA_HOST');
$config['smtp_server'] = 'tls://' . getenv('MDA_HOST');
$config['smtp_port'] = 587;
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
$config['support_url'] = getenv('SUPPORT_URL');
$config['plugins'] = [
'archive',
'zipdownload',
'managesieve',
'password',
];
$config['imap_conn_options'] = [
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => false,
],
];
$config['smtp_conn_options'] = [
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => false,
],
];

View File

@ -0,0 +1,29 @@
<?php
$config['managesieve_port'] = 4190;
$config['managesieve_host'] = '%h';
$config['managesieve_auth_type'] = 'PLAIN';
$config['managesieve_auth_cid'] = null;
$config['managesieve_auth_pw'] = null;
$config['managesieve_usetls'] = true;
$config['managesieve_conn_options'] = [
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => false,
],
];
$config['managesieve_default'] = '/etc/dovecot/sieve/global';
$config['managesieve_script_name'] = 'managesieve';
$config['managesieve_mbox_encoding'] = 'UTF-8';
$config['managesieve_replace_delimiter'] = '';
$config['managesieve_disabled_extensions'] = [];
$config['managesieve_debug'] = false;
$config['managesieve_kolab_master'] = false;
$config['managesieve_filename_extension'] = '.sieve';
$config['managesieve_filename_exceptions'] = [];
$config['managesieve_domains'] = [];
$config['managesieve_vacation'] = 0;
$config['managesieve_vacation_interval'] = 0;
$config['managesieve_vacation_addresses_init'] = false;
$config['managesieve_notify_methods'] = ['mailto'];

View File

@ -0,0 +1,28 @@
<?php
$config['password_driver'] = 'sql';
$config['password_confirm_current'] = true;
$config['password_minimum_length'] = 6;
$config['password_require_nonalpha'] = false;
$config['password_log'] = false;
$config['password_login_exceptions'] = null;
$config['password_hosts'] = null;
$config['password_force_save'] = true;
$config['password_force_new_user'] = false;
$config['password_algorithm'] = 'clear';
$config['password_algorithm_prefix'] = '';
$config['password_blowfish_cost'] = 12;
$config['password_disabled'] = false;
$config['password_db_dsn'] = sprintf(
'mysql://%s:%s@%s/%s',
getenv('MYSQL_USER'),
getenv('MYSQL_PASSWORD'),
getenv('MYSQL_HOST'),
getenv('MYSQL_DATABASE')
);
$config['password_query'] = "UPDATE mail_users JOIN mail_domains ON mail_users.domain_id = mail_domains.id SET password=CONCAT('{SHA256-CRYPT}', ENCRYPT (%p, CONCAT('$5$', SUBSTRING(SHA(RAND()), -16)))) WHERE CONCAT(mail_users.name, '@', mail_domains.name)=%u;";
$config['password_crypt_hash'] = 'md5';
$config['password_idn_ascii'] = false;
$config['password_dovecotpw_with_method'] = false;
$config['password_hash_algorithm'] = 'sha1';
$config['password_hash_base64'] = false;