diff --git a/dockermail/.env b/dockermail/.env new file mode 100644 index 0000000000..68c926dbdc --- /dev/null +++ b/dockermail/.env @@ -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 diff --git a/dockermail/LICENSE b/dockermail/LICENSE new file mode 100644 index 0000000000..730cb1858c --- /dev/null +++ b/dockermail/LICENSE @@ -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. diff --git a/dockermail/README b/dockermail/README index 7851d197d8..49c77099c8 100644 --- a/dockermail/README +++ b/dockermail/README @@ -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/ \ No newline at end of file diff --git a/dockermail/add_account.sh b/dockermail/add_account.sh deleted file mode 100644 index a5279c6773..0000000000 --- a/dockermail/add_account.sh +++ /dev/null @@ -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" - diff --git a/dockermail/db/Dockerfile b/dockermail/db/Dockerfile new file mode 100644 index 0000000000..01e6b2e8e0 --- /dev/null +++ b/dockermail/db/Dockerfile @@ -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 diff --git a/dockermail/db/rootfs/docker-entrypoint-initdb.d/001_mailserver.sql b/dockermail/db/rootfs/docker-entrypoint-initdb.d/001_mailserver.sql new file mode 100644 index 0000000000..1b8018a8e5 --- /dev/null +++ b/dockermail/db/rootfs/docker-entrypoint-initdb.d/001_mailserver.sql @@ -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 */; diff --git a/dockermail/db/rootfs/docker-entrypoint-initdb.d/002_webmail.sql b/dockermail/db/rootfs/docker-entrypoint-initdb.d/002_webmail.sql new file mode 100644 index 0000000000..fee1b6d0e2 --- /dev/null +++ b/dockermail/db/rootfs/docker-entrypoint-initdb.d/002_webmail.sql @@ -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'); diff --git a/dockermail/docker-compose.yml b/dockermail/docker-compose.yml new file mode 100644 index 0000000000..94fdd495fc --- /dev/null +++ b/dockermail/docker-compose.yml @@ -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: diff --git a/dockermail/filter/Dockerfile b/dockermail/filter/Dockerfile new file mode 100644 index 0000000000..8cad291c5a --- /dev/null +++ b/dockermail/filter/Dockerfile @@ -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"] diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/antivirus.conf.templ b/dockermail/filter/rootfs/etc/rspamd/local.d/antivirus.conf.templ new file mode 100644 index 0000000000..78a1c3a4d5 --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/antivirus.conf.templ @@ -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 }} diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/classifier-bayes.conf b/dockermail/filter/rootfs/etc/rspamd/local.d/classifier-bayes.conf new file mode 100644 index 0000000000..d83f163f28 --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/classifier-bayes.conf @@ -0,0 +1 @@ +autolearn = true; diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/dkim_signing.conf b/dockermail/filter/rootfs/etc/rspamd/local.d/dkim_signing.conf new file mode 100644 index 0000000000..0a98225e0d --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/dkim_signing.conf @@ -0,0 +1,2 @@ +path = "/media/dkim/$domain.$selector.key"; +selector_map = "/media/dkim/dkim_selectors.map"; diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/logging.inc b/dockermail/filter/rootfs/etc/rspamd/local.d/logging.inc new file mode 100644 index 0000000000..b2ff81c975 --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/logging.inc @@ -0,0 +1 @@ +type = console diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/metrics.conf b/dockermail/filter/rootfs/etc/rspamd/local.d/metrics.conf new file mode 100644 index 0000000000..514aad55ef --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/metrics.conf @@ -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"; + } +} diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/milter_headers.conf b/dockermail/filter/rootfs/etc/rspamd/local.d/milter_headers.conf new file mode 100644 index 0000000000..915a6e20f6 --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/milter_headers.conf @@ -0,0 +1 @@ +extended_spam_headers = true; diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/options.inc b/dockermail/filter/rootfs/etc/rspamd/local.d/options.inc new file mode 100644 index 0000000000..520d3733f9 --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/options.inc @@ -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" ]; +} + diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/rbl.conf b/dockermail/filter/rootfs/etc/rspamd/local.d/rbl.conf new file mode 100644 index 0000000000..8a2d804f6b --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/rbl.conf @@ -0,0 +1,9 @@ +rbls { + nixspam { + symbol = "RBL_NIXSPAM"; + rbl = "ix.dnsbl.manitu.net"; + returncodes { + RBL_NIXSPAM_BAD = "127.0.0.2"; + } + } +} diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/worker-controller.inc.templ b/dockermail/filter/rootfs/etc/rspamd/local.d/worker-controller.inc.templ new file mode 100644 index 0000000000..c6119ddb1d --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/worker-controller.inc.templ @@ -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}}" diff --git a/dockermail/filter/rootfs/etc/rspamd/local.d/worker-proxy.inc b/dockermail/filter/rootfs/etc/rspamd/local.d/worker-proxy.inc new file mode 100644 index 0000000000..86b2c4e984 --- /dev/null +++ b/dockermail/filter/rootfs/etc/rspamd/local.d/worker-proxy.inc @@ -0,0 +1 @@ +bind_socket = "*:11332"; diff --git a/dockermail/filter/rootfs/usr/local/bin/entrypoint.sh b/dockermail/filter/rootfs/usr/local/bin/entrypoint.sh new file mode 100644 index 0000000000..26570c4ede --- /dev/null +++ b/dockermail/filter/rootfs/usr/local/bin/entrypoint.sh @@ -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 diff --git a/dockermail/mda/Dockerfile b/dockermail/mda/Dockerfile new file mode 100644 index 0000000000..21e967e119 --- /dev/null +++ b/dockermail/mda/Dockerfile @@ -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"] diff --git a/dockermail/mda/rootfs/etc/dovecot/conf.d/10-auth.conf b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-auth.conf new file mode 100644 index 0000000000..2c980e73ce --- /dev/null +++ b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-auth.conf @@ -0,0 +1,3 @@ +auth_mechanisms = plain login + +!include auth-sql.conf.ext diff --git a/dockermail/mda/rootfs/etc/dovecot/conf.d/10-logging.conf b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-logging.conf new file mode 100644 index 0000000000..613ae123c9 --- /dev/null +++ b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-logging.conf @@ -0,0 +1,2 @@ +log_path = /dev/stderr +info_log_path = /dev/stdout diff --git a/dockermail/mda/rootfs/etc/dovecot/conf.d/10-mail.conf b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-mail.conf new file mode 100644 index 0000000000..65ed292084 --- /dev/null +++ b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-mail.conf @@ -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 +} diff --git a/dockermail/mda/rootfs/etc/dovecot/conf.d/10-master.conf.templ b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-master.conf.templ new file mode 100644 index 0000000000..00ed80b027 --- /dev/null +++ b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-master.conf.templ @@ -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 + } +} diff --git a/dockermail/mda/rootfs/etc/dovecot/conf.d/10-ssl.conf b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-ssl.conf new file mode 100644 index 0000000000..2945b113f0 --- /dev/null +++ b/dockermail/mda/rootfs/etc/dovecot/conf.d/10-ssl.conf @@ -0,0 +1,4 @@ +ssl = yes +ssl_cert = ]*\.(bat|com|exe|dll|vbs|docm|doc|dzip)/ REJECT diff --git a/dockermail/mta/rootfs/etc/postfix/mysql-email2email.cf.templ b/dockermail/mta/rootfs/etc/postfix/mysql-email2email.cf.templ new file mode 100644 index 0000000000..e6a8a50dd8 --- /dev/null +++ b/dockermail/mta/rootfs/etc/postfix/mysql-email2email.cf.templ @@ -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' diff --git a/dockermail/mta/rootfs/etc/postfix/mysql-recipient-access.cf.templ b/dockermail/mta/rootfs/etc/postfix/mysql-recipient-access.cf.templ new file mode 100644 index 0000000000..9827cc9f4a --- /dev/null +++ b/dockermail/mta/rootfs/etc/postfix/mysql-recipient-access.cf.templ @@ -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' diff --git a/dockermail/mta/rootfs/etc/postfix/mysql-virtual-alias-maps.cf.templ b/dockermail/mta/rootfs/etc/postfix/mysql-virtual-alias-maps.cf.templ new file mode 100644 index 0000000000..c16448c892 --- /dev/null +++ b/dockermail/mta/rootfs/etc/postfix/mysql-virtual-alias-maps.cf.templ @@ -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' diff --git a/dockermail/mta/rootfs/etc/postfix/mysql-virtual-mailbox-domains.cf.templ b/dockermail/mta/rootfs/etc/postfix/mysql-virtual-mailbox-domains.cf.templ new file mode 100644 index 0000000000..3da2464276 --- /dev/null +++ b/dockermail/mta/rootfs/etc/postfix/mysql-virtual-mailbox-domains.cf.templ @@ -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' diff --git a/dockermail/mta/rootfs/etc/postfix/mysql-virtual-mailbox-maps.cf.templ b/dockermail/mta/rootfs/etc/postfix/mysql-virtual-mailbox-maps.cf.templ new file mode 100644 index 0000000000..2b043c893b --- /dev/null +++ b/dockermail/mta/rootfs/etc/postfix/mysql-virtual-mailbox-maps.cf.templ @@ -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 diff --git a/dockermail/mta/rootfs/etc/supervisord.conf b/dockermail/mta/rootfs/etc/supervisord.conf new file mode 100644 index 0000000000..2cfd5f40d8 --- /dev/null +++ b/dockermail/mta/rootfs/etc/supervisord.conf @@ -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 diff --git a/dockermail/mta/rootfs/usr/local/bin/entrypoint.sh b/dockermail/mta/rootfs/usr/local/bin/entrypoint.sh new file mode 100644 index 0000000000..56cc25a221 --- /dev/null +++ b/dockermail/mta/rootfs/usr/local/bin/entrypoint.sh @@ -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 diff --git a/dockermail/send_mail.sh b/dockermail/send_mail.sh deleted file mode 100644 index 823901a3e3..0000000000 --- a/dockermail/send_mail.sh +++ /dev/null @@ -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 diff --git a/dockermail/setup.sh b/dockermail/setup.sh deleted file mode 100644 index 709e471bd1..0000000000 --- a/dockermail/setup.sh +++ /dev/null @@ -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 diff --git a/dockermail/ssl/Dockerfile b/dockermail/ssl/Dockerfile new file mode 100644 index 0000000000..ca9d8c2578 --- /dev/null +++ b/dockermail/ssl/Dockerfile @@ -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"] diff --git a/dockermail/ssl/create_tls.sh b/dockermail/ssl/create_tls.sh new file mode 100644 index 0000000000..8354088616 --- /dev/null +++ b/dockermail/ssl/create_tls.sh @@ -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 diff --git a/dockermail/test/Dockerfile b/dockermail/test/Dockerfile new file mode 100644 index 0000000000..d83a28e560 --- /dev/null +++ b/dockermail/test/Dockerfile @@ -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"] diff --git a/dockermail/test/rootfs/usr/local/bin/run-tests.sh b/dockermail/test/rootfs/usr/local/bin/run-tests.sh new file mode 100644 index 0000000000..eb59fc995b --- /dev/null +++ b/dockermail/test/rootfs/usr/local/bin/run-tests.sh @@ -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 diff --git a/dockermail/test/rootfs/usr/share/tests/001_tls.bats b/dockermail/test/rootfs/usr/share/tests/001_tls.bats new file mode 100644 index 0000000000..83e76e78f8 --- /dev/null +++ b/dockermail/test/rootfs/usr/share/tests/001_tls.bats @@ -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 ] +} diff --git a/dockermail/test/rootfs/usr/share/tests/002_database.bats b/dockermail/test/rootfs/usr/share/tests/002_database.bats new file mode 100644 index 0000000000..0085f7d9b7 --- /dev/null +++ b/dockermail/test/rootfs/usr/share/tests/002_database.bats @@ -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 ] +} diff --git a/dockermail/test/rootfs/usr/share/tests/003_mta.bats b/dockermail/test/rootfs/usr/share/tests/003_mta.bats new file mode 100644 index 0000000000..a95c6f9e0b --- /dev/null +++ b/dockermail/test/rootfs/usr/share/tests/003_mta.bats @@ -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 ] +} diff --git a/dockermail/test/rootfs/usr/share/tests/004_web.bats b/dockermail/test/rootfs/usr/share/tests/004_web.bats new file mode 100644 index 0000000000..bc6e8c22e3 --- /dev/null +++ b/dockermail/test/rootfs/usr/share/tests/004_web.bats @@ -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 ] +} diff --git a/dockermail/test/rootfs/usr/share/tests/005_mda.bats b/dockermail/test/rootfs/usr/share/tests/005_mda.bats new file mode 100644 index 0000000000..d474f86a76 --- /dev/null +++ b/dockermail/test/rootfs/usr/share/tests/005_mda.bats @@ -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" = "" ] +} diff --git a/dockermail/test/rootfs/usr/share/tests/006_docker.bats b/dockermail/test/rootfs/usr/share/tests/006_docker.bats new file mode 100644 index 0000000000..284aad0b66 --- /dev/null +++ b/dockermail/test/rootfs/usr/share/tests/006_docker.bats @@ -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" = "" ] +} diff --git a/dockermail/test/rootfs/usr/share/tests/007_relayhost.bats b/dockermail/test/rootfs/usr/share/tests/007_relayhost.bats new file mode 100644 index 0000000000..ea28ad830c --- /dev/null +++ b/dockermail/test/rootfs/usr/share/tests/007_relayhost.bats @@ -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" ] +} diff --git a/dockermail/test/rootfs/usr/share/tests/008_dkim.bats b/dockermail/test/rootfs/usr/share/tests/008_dkim.bats new file mode 100644 index 0000000000..063e8a2825 --- /dev/null +++ b/dockermail/test/rootfs/usr/share/tests/008_dkim.bats @@ -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 ] +} diff --git a/dockermail/update.sh b/dockermail/update.sh deleted file mode 100644 index 01b527ced9..0000000000 --- a/dockermail/update.sh +++ /dev/null @@ -1,6 +0,0 @@ -#|/bin/bash - -pushd ./mailserver || exit -docker-compose down -docker pull tvial/docker-mailserver:latest -docker-compose up -d mail diff --git a/dockermail/virus/Dockerfile b/dockermail/virus/Dockerfile new file mode 100644 index 0000000000..cc03e42172 --- /dev/null +++ b/dockermail/virus/Dockerfile @@ -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"] diff --git a/dockermail/virus/contrib/unofficial-sigs/Dockerfile b/dockermail/virus/contrib/unofficial-sigs/Dockerfile new file mode 100644 index 0000000000..4ec2abc2a8 --- /dev/null +++ b/dockermail/virus/contrib/unofficial-sigs/Dockerfile @@ -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"] diff --git a/dockermail/virus/rootfs/etc/clamav/clamd.conf b/dockermail/virus/rootfs/etc/clamav/clamd.conf new file mode 100644 index 0000000000..a7607f8267 --- /dev/null +++ b/dockermail/virus/rootfs/etc/clamav/clamd.conf @@ -0,0 +1,2 @@ +TCPSocket 3310 +Foreground true diff --git a/dockermail/virus/rootfs/etc/clamav/freshclam.conf b/dockermail/virus/rootfs/etc/clamav/freshclam.conf new file mode 100644 index 0000000000..b81237da50 --- /dev/null +++ b/dockermail/virus/rootfs/etc/clamav/freshclam.conf @@ -0,0 +1,4 @@ +DatabaseOwner clamav +DatabaseMirror database.clamav.net +ScriptedUpdates yes +NotifyClamd /etc/clamav/clamd.conf diff --git a/dockermail/virus/rootfs/usr/local/bin/entrypoint.sh b/dockermail/virus/rootfs/usr/local/bin/entrypoint.sh new file mode 100644 index 0000000000..59e5af3365 --- /dev/null +++ b/dockermail/virus/rootfs/usr/local/bin/entrypoint.sh @@ -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 diff --git a/dockermail/web/Dockerfile b/dockermail/web/Dockerfile new file mode 100644 index 0000000000..a16ae2947b --- /dev/null +++ b/dockermail/web/Dockerfile @@ -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"] diff --git a/dockermail/web/README.md b/dockermail/web/README.md new file mode 100644 index 0000000000..fadde33c32 --- /dev/null +++ b/dockermail/web/README.md @@ -0,0 +1,6 @@ +# mailserver-web + +This image contains + +[roundcube](https://roundcube.net) +and [mailserver-admin](https://github.com/jeboehm/mailserver-admin). diff --git a/dockermail/web/rootfs/etc/nginx/sites-enabled/10-docker.conf b/dockermail/web/rootfs/etc/nginx/sites-enabled/10-docker.conf new file mode 100644 index 0000000000..1f234cd3a6 --- /dev/null +++ b/dockermail/web/rootfs/etc/nginx/sites-enabled/10-docker.conf @@ -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; + } +} diff --git a/dockermail/web/rootfs/usr/local/bin/entrypoint.sh b/dockermail/web/rootfs/usr/local/bin/entrypoint.sh new file mode 100644 index 0000000000..e57148e0ef --- /dev/null +++ b/dockermail/web/rootfs/usr/local/bin/entrypoint.sh @@ -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 diff --git a/dockermail/web/rootfs/usr/local/bin/fixtures.sh b/dockermail/web/rootfs/usr/local/bin/fixtures.sh new file mode 100644 index 0000000000..ab18784ae2 --- /dev/null +++ b/dockermail/web/rootfs/usr/local/bin/fixtures.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +dockerize \ + -wait tcp://web:80 \ + -wait tcp://${MYSQL_HOST}:3306 \ + -timeout ${WAITSTART_TIMEOUT} \ + ${@} diff --git a/dockermail/web/rootfs/usr/local/bin/setup.sh b/dockermail/web/rootfs/usr/local/bin/setup.sh new file mode 100644 index 0000000000..6d8070206b --- /dev/null +++ b/dockermail/web/rootfs/usr/local/bin/setup.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +/usr/local/bin/fixtures.sh \ + /opt/manager/bin/console init:setup diff --git a/dockermail/web/rootfs/var/www/html/webmail/config/config.inc.php b/dockermail/web/rootfs/var/www/html/webmail/config/config.inc.php new file mode 100644 index 0000000000..a047df7f77 --- /dev/null +++ b/dockermail/web/rootfs/var/www/html/webmail/config/config.inc.php @@ -0,0 +1,39 @@ + [ + '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, + ], +]; diff --git a/dockermail/web/rootfs/var/www/html/webmail/plugins/managesieve/config.inc.php b/dockermail/web/rootfs/var/www/html/webmail/plugins/managesieve/config.inc.php new file mode 100644 index 0000000000..ac208df5dd --- /dev/null +++ b/dockermail/web/rootfs/var/www/html/webmail/plugins/managesieve/config.inc.php @@ -0,0 +1,29 @@ + [ + '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']; diff --git a/dockermail/web/rootfs/var/www/html/webmail/plugins/password/config.inc.php b/dockermail/web/rootfs/var/www/html/webmail/plugins/password/config.inc.php new file mode 100644 index 0000000000..1c25db540f --- /dev/null +++ b/dockermail/web/rootfs/var/www/html/webmail/plugins/password/config.inc.php @@ -0,0 +1,28 @@ +