├── .dockerignore ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── stale.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── docker-compose.sample.yml ├── rootfs ├── etc │ ├── clamav │ │ └── unofficial-sigs │ │ │ ├── master.conf │ │ │ └── os.conf │ ├── cron.d │ │ ├── counters │ │ └── fetchmail │ ├── dovecot │ │ ├── conf.d │ │ │ ├── 10-auth.conf │ │ │ ├── 10-logging.conf │ │ │ ├── 10-mail.conf │ │ │ ├── 10-master.conf │ │ │ ├── 10-ssl.conf │ │ │ ├── 15-lda.conf │ │ │ ├── 15-mailboxes.conf │ │ │ ├── 20-imap.conf │ │ │ ├── 20-lmtp.conf │ │ │ ├── 20-pop3.conf │ │ │ ├── 90-quota.conf │ │ │ ├── 90-sieve.conf │ │ │ ├── auth-ldap.conf.ext │ │ │ └── auth-sql.conf.ext │ │ ├── dovecot-dict-sql.conf.ext │ │ ├── dovecot-ldap-master.conf.ext │ │ ├── dovecot-ldap.conf.ext │ │ ├── dovecot-sql.conf.ext │ │ ├── dovecot.conf │ │ └── sieve │ │ │ ├── report-ham.sieve │ │ │ ├── report-spam.sieve │ │ │ ├── rspamd-pipe-ham.sh │ │ │ └── rspamd-pipe-spam.sh │ ├── mailname │ ├── postfix │ │ ├── ffdhe2048.pem │ │ ├── header_checks │ │ ├── ldap │ │ │ ├── sender-login-maps.cf │ │ │ ├── virtual-alias-maps.cf │ │ │ ├── virtual-forward-maps.cf │ │ │ ├── virtual-group-maps.cf │ │ │ ├── virtual-mailbox-domains.cf │ │ │ └── virtual-mailbox-maps.cf │ │ ├── main.cf │ │ ├── master.cf │ │ ├── sql │ │ │ ├── sender-login-maps.cf │ │ │ ├── virtual-alias-domain-catchall-maps.cf │ │ │ ├── virtual-alias-domain-mailbox-maps.cf │ │ │ ├── virtual-alias-domain-maps.cf │ │ │ ├── virtual-alias-maps.cf │ │ │ ├── virtual-mailbox-domains.cf │ │ │ └── virtual-mailbox-maps.cf │ │ └── virtual │ ├── postfixadmin │ │ └── fetchmail.conf │ ├── rspamd │ │ └── local.d │ │ │ ├── antivirus.conf │ │ │ ├── arc.conf │ │ │ ├── dkim_signing.conf │ │ │ ├── groups.conf │ │ │ ├── logging.inc │ │ │ ├── milter_headers.conf │ │ │ ├── mime_types.conf │ │ │ ├── mx_check.conf │ │ │ ├── neural.conf │ │ │ ├── neural_group.conf │ │ │ ├── options.inc │ │ │ ├── ratelimit.conf │ │ │ ├── redis.conf │ │ │ ├── settings.conf │ │ │ ├── statistic.conf │ │ │ ├── worker-controller.inc │ │ │ ├── worker-fuzzy.inc │ │ │ ├── worker-normal.inc │ │ │ └── worker-proxy.inc │ ├── rsyslog │ │ └── rsyslog.conf │ ├── unbound │ │ └── unbound.conf │ └── zeyple │ │ └── zeyple.conf ├── services │ ├── .s6-svscan │ │ └── finish │ ├── _parent │ │ └── run │ ├── cert_watcher │ │ ├── down │ │ └── run │ ├── clamd │ │ ├── down │ │ └── run │ ├── cron │ │ ├── down │ │ └── run │ ├── dovecot │ │ ├── down │ │ └── run │ ├── freshclam │ │ ├── down │ │ └── run │ ├── postfix │ │ ├── down │ │ └── run │ ├── rspamd │ │ ├── down │ │ └── run │ ├── rsyslogd │ │ ├── down │ │ └── run │ └── unbound │ │ ├── down │ │ └── run └── usr │ └── local │ └── bin │ ├── certs_helper.sh │ ├── clamav-unofficial-sigs.sh │ ├── dumpcerts.acme.v1.sh │ ├── dumpcerts.acme.v2.sh │ ├── dumpcerts.traefik.v2.sh │ ├── encryption.sh │ ├── fetchmail.pl │ ├── quota-warning.sh │ ├── run.sh │ ├── setup.sh │ ├── watcher.py │ └── zeyple.py ├── sample.env ├── test ├── config │ ├── ldap │ │ └── struct.ldif │ ├── mariadb │ │ ├── bind.cnf │ │ └── struct.sql │ └── postgres │ │ └── struct.sql ├── share │ ├── clamav │ │ └── unofficial-sigs │ │ │ └── user.conf │ ├── dovecot │ │ └── conf.d │ │ │ ├── 10-custom.conf │ │ │ └── 20-custom.conf │ ├── letsencrypt │ │ └── live │ │ │ └── mail.domain.tld │ │ │ ├── cert.pem │ │ │ ├── chain.pem │ │ │ ├── fullchain.pem │ │ │ └── privkey.pem │ ├── passwd │ │ ├── mariadb │ │ ├── postgres │ │ ├── redis │ │ └── rspamd │ ├── postfix │ │ ├── custom.conf │ │ ├── custom.ecdsa.conf │ │ └── sender_access │ ├── sieve │ │ └── custom.sieve │ ├── ssl │ │ ├── ecdsa │ │ │ └── selfsigned │ │ │ │ ├── cert.pem │ │ │ │ └── privkey.pem │ │ └── rsa │ │ │ └── selfsigned │ │ │ ├── cert.pem │ │ │ └── privkey.pem │ ├── tests │ │ ├── auth │ │ │ ├── imap-auth-master-wrong.txt │ │ │ ├── imap-auth-master.txt │ │ │ ├── imap-auth-wrong.txt │ │ │ ├── imap-auth.txt │ │ │ ├── pop3-auth-wrong.txt │ │ │ ├── pop3-auth.txt │ │ │ ├── smtp-auth-login-wrong.txt │ │ │ ├── smtp-auth-login.txt │ │ │ ├── smtp-auth-plain-wrong.txt │ │ │ └── smtp-auth-plain.txt │ │ ├── clamav │ │ │ ├── test1.eml │ │ │ ├── test2.eml │ │ │ └── test3.eml │ │ ├── email-templates │ │ │ ├── external-spam-to-existing-user.txt │ │ │ ├── external-to-existing-alias-forward.txt │ │ │ ├── external-to-existing-alias-group.txt │ │ │ ├── external-to-existing-alias.txt │ │ │ ├── external-to-existing-system-account.txt │ │ │ ├── external-to-existing-user-spam-learning.txt │ │ │ ├── external-to-existing-user.txt │ │ │ ├── external-to-non-existing-user.txt │ │ │ ├── external-to-valid-user-subaddress-with-default-separator.txt │ │ │ ├── external-to-valid-user-subaddress.txt │ │ │ ├── external-virus-to-existing-user.txt │ │ │ ├── internal-rejected-user-to-existing-user.txt │ │ │ └── internal-user-to-existing-user.txt │ │ └── sieve │ │ │ └── trigger-spam-ham-learning.txt │ └── traefik │ │ ├── acme.v1.json │ │ └── acme.v2.json └── tests.bats └── traefik.sample.toml /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | test/ 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### Classification 2 | _Please delete options that are not relevant._ 3 | 4 | * [X] Question 5 | * [ ] Deliverability issue 6 | * [ ] Crash/Hang/Data loss 7 | * [ ] Serious bug 8 | * [ ] Minor bug 9 | * [ ] Vulnerability 10 | * [ ] Feature/Enhancement 11 | * [ ] Documentation 12 | 13 | #### Reproducibility 14 | _Please delete options that are not relevant._ 15 | 16 | * [X] Always 17 | * [ ] Sometimes 18 | * [ ] Rarely 19 | * [ ] Unable 20 | * [ ] I didn’t try 21 | * [ ] Not applicable 22 | 23 | #### Docker information 24 | ``` 25 | docker info 26 | docker images hardware/mailserver --digests --filter "dangling=false" 27 | ``` 28 | 29 | #### Description 30 | Briefly describe the problem you are having in a few lines. 31 | 32 | 33 | #### Steps to reproduce 34 | 1. 35 | 2. 36 | 3. 37 | 38 | #### Expected results 39 | 40 | 41 | #### Actual results 42 | 43 | 44 | #### Debugging information 45 | ``` 46 | docker logs mailserver 47 | ``` 48 | 49 | #### Configuration (docker-compose.yml, traefik.toml...etc) 50 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thanks for submitting a pull request ! Please provide enough information so that others can review your changes. 2 | 3 | For more information, see the `CONTRIBUTING` guide. 4 | 5 | ## Description 6 | 7 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 8 | 9 | Fixes # (issue) 10 | 11 | * What is the current behavior (you can also link to an open issue here) ? 12 | * What is the new behavior (if this is a feature change) ? 13 | 14 | ## Type of change 15 | _Please delete options that are not relevant._ 16 | 17 | - [ ] Bug fix (non-breaking change which fixes an issue) 18 | - [ ] New feature (non-breaking change which adds functionality) 19 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 20 | - [ ] Documentation only 21 | - [ ] Performance improvement 22 | - [ ] Refactoring (a change that neither fixes a bug nor adds a feature) 23 | - [ ] Test (adding missing tests or correcting existing ones) 24 | - [ ] Other... 25 | 26 | ## Status 27 | 28 | - [ ] Ready 29 | - [ ] In development 30 | - [ ] Hold 31 | 32 | ## Todo List 33 | - [ ] Implementation 34 | - [ ] Tests 35 | - [ ] Documentation 36 | - [ ] ... 37 | 38 | ## How has this been tested ? 39 | 40 | Describe the tests that you ran to verify your changes. Provide instructions so we can reproduce and please also list any relevant details of your test configuration. 41 | 42 | - [ ] Test A 43 | - [ ] Test B 44 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | daysUntilStale: 182 2 | daysUntilClose: 7 3 | exemptLabels: 4 | - Pinned 5 | staleLabel: Stale 6 | markComment: > 7 | This issue has been automatically marked as stale because it has not had 8 | recent activity. It will be closed if no further activity occurs. Thank you 9 | for your contributions. 10 | closeComment: false 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docker-compose.mysql.yml 2 | docker-compose.pgsql.yml 3 | traefik.toml 4 | .env 5 | certs 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/bats"] 2 | path = test/bats 3 | url = https://github.com/sstephenson/bats 4 | [submodule "test/test_helper/bats-support"] 5 | path = test/test_helper/bats-support 6 | url = https://github.com/ztombol/bats-support 7 | [submodule "test/test_helper/bats-assert"] 8 | path = test/test_helper/bats-assert 9 | url = https://github.com/ztombol/bats-assert 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | sudo: required 3 | services: 4 | - docker 5 | script: 6 | - make all 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@meshup.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First off, thanks for taking the time to contribute. Pull requests are always welcome ! :thumbsup: 4 | 5 | The following is a set of guidelines for contributing to this repository. 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## How to contribute 9 | 10 | - Fork this repository 11 | - Create a new feature branch for a new functionality or bugfix 12 | - Code... 13 | - Add integration tests in test/tests.bats 14 | - Use `make` to build image locally and run tests 15 | - Document your improvements 16 | - Commit your changes 17 | - Push your code and open a new pull request 18 | - Use [issues](https://github.com/hardware/mailserver/issues) for any questions 19 | 20 | ## Licensing 21 | 22 | Please note, this repository is made available under the MIT license, so any contributions must be compatible with that license. If you're not sure about that, just ask. 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hardware/debian-mail-overlay:latest 2 | 3 | LABEL description "Simple and full-featured mail server using Docker" \ 4 | maintainer="Hardware " 5 | 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | ENV PYTHONUNBUFFERED=1 8 | 9 | RUN apt-get update && apt-get install -y -q --no-install-recommends \ 10 | postfix postfix-pgsql postfix-mysql postfix-ldap postfix-pcre libsasl2-modules \ 11 | dovecot-core dovecot-imapd dovecot-lmtpd dovecot-pgsql dovecot-mysql dovecot-ldap dovecot-sieve dovecot-managesieved dovecot-pop3d \ 12 | fetchmail libdbi-perl libdbd-pg-perl libdbd-mysql-perl liblockfile-simple-perl \ 13 | clamav clamav-daemon \ 14 | python3-pip python3-setuptools python3-wheel \ 15 | rsyslog dnsutils curl unbound jq rsync \ 16 | inotify-tools \ 17 | && rm -rf /var/spool/postfix \ 18 | && ln -s /var/mail/postfix/spool /var/spool/postfix \ 19 | && apt-get autoremove -y \ 20 | && apt-get clean \ 21 | && rm -rf /tmp/* /var/lib/apt/lists/* /var/cache/debconf/*-old \ 22 | && pip3 install watchdog 23 | 24 | EXPOSE 25 143 465 587 993 4190 11334 25 | COPY rootfs / 26 | RUN chmod +x /usr/local/bin/* /services/*/run /services/.s6-svscan/finish 27 | CMD ["run.sh"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 Hardware 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME = hardware/mailserver:testing 2 | 3 | all: build-no-cache init fixtures run clean 4 | all-fast: build init fixtures run clean 5 | no-build: init fixtures run clean 6 | 7 | build-no-cache: 8 | docker build --no-cache -t $(NAME) . 9 | 10 | build: 11 | docker build -t $(NAME) . 12 | 13 | init: 14 | -docker rm -f \ 15 | mariadb postgres redis openldap \ 16 | mailserver_default mailserver_reverse mailserver_ecdsa mailserver_ldap mailserver_ldap2 \ 17 | mailserver_traefik_acmev1 mailserver_traefik_acmev2 18 | 19 | sleep 2 20 | 21 | docker run \ 22 | -d \ 23 | --name mariadb \ 24 | -e MYSQL_RANDOM_ROOT_PASSWORD=yes \ 25 | -e MYSQL_DATABASE=postfix \ 26 | -e MYSQL_USER=postfix \ 27 | -e MYSQL_PASSWORD=testpasswd \ 28 | -v "`pwd`/test/config/mariadb/struct.sql":/docker-entrypoint-initdb.d/struct.sql \ 29 | -v "`pwd`/test/config/mariadb/bind.cnf":/etc/mysql/conf.d/bind.cnf \ 30 | -t mysql:5.7 31 | 32 | docker run \ 33 | -d \ 34 | --name postgres \ 35 | -e POSTGRES_DB=postfix \ 36 | -e POSTGRES_USER=postfix \ 37 | -e POSTGRES_PASSWORD=testpasswd \ 38 | -v "`pwd`/test/config/postgres":/docker-entrypoint-initdb.d \ 39 | -t postgres:10.5-alpine 40 | 41 | docker run \ 42 | -d \ 43 | --name redis \ 44 | -t redis:4.0-alpine 45 | 46 | docker run \ 47 | -d \ 48 | --name openldap \ 49 | -e LDAP_ORGANISATION="Test LDAP" \ 50 | -e LDAP_DOMAIN="domain.tld" \ 51 | -e LDAP_ADMIN_PASSWORD="testpasswd" \ 52 | -e LDAP_TLS=false \ 53 | -v "`pwd`/test/config/ldap/struct.ldif":/container/service/slapd/assets/config/bootstrap/ldif/custom/struct.ldif \ 54 | -t osixia/openldap:1.2.2 --copy-service 55 | 56 | sleep 10 57 | 58 | docker run \ 59 | -d \ 60 | --name mailserver_default \ 61 | --link mariadb:mariadb \ 62 | --link redis:redis \ 63 | -e DBPASS=testpasswd \ 64 | -e RSPAMD_PASSWORD=testpasswd \ 65 | -e VMAILUID=`id -u` \ 66 | -e VMAILGID=`id -g` \ 67 | -e ADD_DOMAINS=domain2.tld,domain3.tld \ 68 | -e RECIPIENT_DELIMITER=: \ 69 | -e TESTING=true \ 70 | -v "`pwd`/test/share/tests":/tmp/tests \ 71 | -v "`pwd`/test/share/ssl/rsa":/var/mail/ssl \ 72 | -v "`pwd`/test/share/postfix/custom.conf":/var/mail/postfix/custom.conf \ 73 | -v "`pwd`/test/share/postfix/sender_access":/var/mail/postfix/sender_access \ 74 | -v "`pwd`/test/share/dovecot/conf.d":/var/mail/dovecot/conf.d \ 75 | -v "`pwd`/test/share/clamav/unofficial-sigs/user.conf":/var/mail/clamav-unofficial-sigs/user.conf \ 76 | -h mail.domain.tld \ 77 | -t $(NAME) 78 | 79 | docker run \ 80 | -d \ 81 | --name mailserver_reverse \ 82 | --link postgres:postgres \ 83 | --link redis:redis \ 84 | -e FQDN=mail.domain.tld \ 85 | -e DOMAIN=domain.tld \ 86 | -e DBDRIVER=pgsql \ 87 | -e DBHOST=postgres \ 88 | -e DBPORT=5432 \ 89 | -e DBPASS=/tmp/passwd/postgres \ 90 | -e REDIS_HOST=redis \ 91 | -e REDIS_PORT=6379 \ 92 | -e REDIS_PASS=/tmp/passwd/redis \ 93 | -e RSPAMD_PASSWORD=/tmp/passwd/rspamd \ 94 | -e VMAILUID=`id -u` \ 95 | -e VMAILGID=`id -g` \ 96 | -e VMAIL_SUBDIR=subdir \ 97 | -e RELAY_NETWORKS="192.168.0.0/16 172.16.0.0/12 10.0.0.0/8" \ 98 | -e DISABLE_CLAMAV=true \ 99 | -e DISABLE_SIEVE=true \ 100 | -e DISABLE_SIGNING=true \ 101 | -e DISABLE_GREYLISTING=true \ 102 | -e DISABLE_RATELIMITING=true \ 103 | -e DISABLE_DNS_RESOLVER=true \ 104 | -e ENABLE_POP3=true \ 105 | -e ENABLE_ENCRYPTION=true \ 106 | -e ENABLE_FETCHMAIL=true \ 107 | -e OPENDKIM_KEY_LENGTH=4096 \ 108 | -e TESTING=true \ 109 | -v "`pwd`/test/share/tests":/tmp/tests \ 110 | -v "`pwd`/test/share/passwd":/tmp/passwd \ 111 | -v "`pwd`/test/share/ssl/rsa":/var/mail/ssl \ 112 | -v "`pwd`/test/share/sieve/custom.sieve":/var/mail/sieve/custom.sieve \ 113 | -v "`pwd`/test/share/letsencrypt":/etc/letsencrypt \ 114 | -t $(NAME) 115 | 116 | docker run \ 117 | -d \ 118 | --name mailserver_ldap \ 119 | --link openldap \ 120 | --link redis:redis \ 121 | -e DBDRIVER=ldap \ 122 | -e DBHOST=openldap \ 123 | -e DBPORT=389 \ 124 | -e LDAP_BIND_DN="cn=admin,dc=domain,dc=tld" \ 125 | -e LDAP_BIND_PW="testpasswd" \ 126 | -e LDAP_DEFAULT_SEARCH_BASE="o=mx,dc=domain,dc=tld" \ 127 | -e LDAP_DOMAIN_FILTER="(&(mail=*@%s)(objectClass=mailAccount))" \ 128 | -e LDAP_DOMAIN_ATTRIBUTE="mail" \ 129 | -e LDAP_DOMAIN_FORMAT="%d" \ 130 | -e LDAP_MAILBOX_FILTER="(&(mail=%s)(objectClass=mailAccount))" \ 131 | -e LDAP_MAILBOX_ATTRIBUTE="mail" \ 132 | -e LDAP_MAILBOX_FORMAT="/var/mail/vhosts/%d/%s/mail/" \ 133 | -e LDAP_ALIAS_FILTER="(&(mailalias=%s)(objectClass=mailAccount))" \ 134 | -e LDAP_ALIAS_ATTRIBUTE="mail" \ 135 | -e LDAP_FORWARD_FILTER="(&(mailalias=%s)(objectClass=mailAlias))" \ 136 | -e LDAP_FORWARD_ATTRIBUTE="mail" \ 137 | -e LDAP_GROUP_FILTER="(&(mail=%s)(objectClass=mailGroup))" \ 138 | -e LDAP_GROUP_ATTRIBUTE="uid" \ 139 | -e LDAP_SENDER_FILTER="(&(|(mail=%s)(mailalias=%s))(objectClass=mailAccount))" \ 140 | -e LDAP_SENDER_ATTRIBUTE="mail" \ 141 | -e LDAP_DOVECOT_USER_ATTRS="=home=/var/mail/vhosts/%d/%n/,=mail=maildir:/var/mail/vhosts/%d/%n/mail/,mailuserquota=quota_rule=*:bytes=%\$$" \ 142 | -e LDAP_DOVECOT_USER_FILTER="(&(mail=%u)(objectClass=mailAccount))" \ 143 | -e LDAP_DOVECOT_PASS_ATTRS="mail=user,userPassword=password" \ 144 | -e LDAP_DOVECOT_PASS_FILTER="(&(mail=%u)(objectClass=mailAccount))" \ 145 | -e LDAP_DOVECOT_ITERATE_ATTRS="mail=user" \ 146 | -e LDAP_DOVECOT_ITERATE_FILTER="(objectClass=mailAccount)" \ 147 | -e VMAILUID=`id -u` \ 148 | -e VMAILGID=`id -g` \ 149 | -e RSPAMD_PASSWORD=testpasswd \ 150 | -e ADD_DOMAINS=domain2.tld,domain3.tld \ 151 | -e RECIPIENT_DELIMITER=: \ 152 | -e TESTING=true \ 153 | -v "`pwd`/test/share/tests":/tmp/tests \ 154 | -v "`pwd`/test/share/ssl/rsa":/var/mail/ssl \ 155 | -v "`pwd`/test/share/postfix/custom.conf":/var/mail/postfix/custom.conf \ 156 | -v "`pwd`/test/share/postfix/sender_access":/var/mail/postfix/sender_access \ 157 | -v "`pwd`/test/share/dovecot/conf.d":/var/mail/dovecot/conf.d \ 158 | -v "`pwd`/test/share/clamav/unofficial-sigs/user.conf":/var/mail/clamav-unofficial-sigs/user.conf \ 159 | -h mail.domain.tld \ 160 | -t $(NAME) 161 | 162 | docker run \ 163 | -d \ 164 | --name mailserver_ldap2 \ 165 | --link openldap \ 166 | --link redis:redis \ 167 | -e DBDRIVER=ldap \ 168 | -e DBHOST=openldap \ 169 | -e DBPORT=389 \ 170 | -e LDAP_BIND_DN="cn=admin,dc=domain,dc=tld" \ 171 | -e LDAP_BIND_PW="testpasswd" \ 172 | -e LDAP_DEFAULT_SEARCH_BASE="o=mx,dc=domain,dc=tld" \ 173 | -e LDAP_DOMAIN_FILTER="(&(mail=*@%s)(objectClass=mailAccount))" \ 174 | -e LDAP_DOMAIN_ATTRIBUTE="mail" \ 175 | -e LDAP_DOMAIN_FORMAT="%d" \ 176 | -e LDAP_MAILBOX_FILTER="(&(mail=%s)(objectClass=mailAccount))" \ 177 | -e LDAP_MAILBOX_ATTRIBUTE="mail" \ 178 | -e LDAP_MAILBOX_FORMAT="/var/mail/vhosts/%d/%s/mail/" \ 179 | -e LDAP_ALIAS_FILTER="(&(mailalias=%s)(objectClass=mailAccount))" \ 180 | -e LDAP_ALIAS_ATTRIBUTE="mail" \ 181 | -e LDAP_SENDER_FILTER="(&(|(mail=%s)(mailalias=%s))(objectClass=mailAccount))" \ 182 | -e LDAP_SENDER_ATTRIBUTE="mail" \ 183 | -e LDAP_DOVECOT_USER_ATTRS="=home=/var/mail/vhosts/%d/%n/,=mail=maildir:/var/mail/vhosts/%d/%n/mail/,mailuserquota=quota_rule=*:bytes=%\$$" \ 184 | -e LDAP_DOVECOT_USER_FILTER="(&(mail=%u)(objectClass=mailAccount))" \ 185 | -e LDAP_DOVECOT_PASS_ATTRS="mail=user,userPassword=password" \ 186 | -e LDAP_DOVECOT_PASS_FILTER="(&(mail=%u)(objectClass=mailAccount))" \ 187 | -e LDAP_DOVECOT_ITERATE_ATTRS="mail=user" \ 188 | -e LDAP_DOVECOT_ITERATE_FILTER="(objectClass=mailAccount)" \ 189 | -e LDAP_MASTER_USER_ENABLED=true \ 190 | -e LDAP_DOVECOT_MASTER_PASS_ATTRS="mail=user,userPassword=password" \ 191 | -e LDAP_DOVECOT_MASTER_PASS_FILTER="(&(mail=%u)(st=%{login_user})(objectClass=mailAccount))" \ 192 | -e DISABLE_CLAMAV=true \ 193 | -e DISABLE_SIEVE=true \ 194 | -e DISABLE_SIGNING=true \ 195 | -e DISABLE_GREYLISTING=true \ 196 | -e DISABLE_RATELIMITING=true \ 197 | -e DISABLE_DNS_RESOLVER=true \ 198 | -e VMAILUID=`id -u` \ 199 | -e VMAILGID=`id -g` \ 200 | -e RSPAMD_PASSWORD=testpasswd \ 201 | -e ADD_DOMAINS=domain2.tld,domain3.tld \ 202 | -e RECIPIENT_DELIMITER=: \ 203 | -e TESTING=true \ 204 | -v "`pwd`/test/share/tests":/tmp/tests \ 205 | -v "`pwd`/test/share/ssl/rsa":/var/mail/ssl \ 206 | -v "`pwd`/test/share/postfix/custom.conf":/var/mail/postfix/custom.conf \ 207 | -v "`pwd`/test/share/postfix/sender_access":/var/mail/postfix/sender_access \ 208 | -v "`pwd`/test/share/dovecot/conf.d":/var/mail/dovecot/conf.d \ 209 | -v "`pwd`/test/share/clamav/unofficial-sigs/user.conf":/var/mail/clamav-unofficial-sigs/user.conf \ 210 | -h mail.domain.tld \ 211 | -t $(NAME) 212 | 213 | docker run \ 214 | -d \ 215 | --name mailserver_ecdsa \ 216 | --link mariadb:mariadb \ 217 | --link redis:redis \ 218 | -e DBPASS=testpasswd \ 219 | -e RSPAMD_PASSWORD=testpasswd \ 220 | -e VMAILUID=`id -u` \ 221 | -e VMAILGID=`id -g` \ 222 | -e DISABLE_CLAMAV=true \ 223 | -e DISABLE_RSPAMD_MODULE=rbl,mx_check,url_redirector \ 224 | -e WHITELIST_SPAM_ADDRESSES=test@example.com,another@domain.tld \ 225 | -e TESTING=true \ 226 | -v "`pwd`/test/share/ssl/ecdsa":/var/mail/ssl \ 227 | -v "`pwd`/test/share/postfix/custom.ecdsa.conf":/var/mail/postfix/custom.conf \ 228 | -h mail.domain.tld \ 229 | -t $(NAME) 230 | 231 | docker run \ 232 | -d \ 233 | --name mailserver_traefik_acmev1 \ 234 | --link mariadb:mariadb \ 235 | --link redis:redis \ 236 | -e DEBUG_MODE=dovecot,postfix \ 237 | -e DBPASS=testpasswd \ 238 | -e RSPAMD_PASSWORD=testpasswd \ 239 | -e VMAILUID=`id -u` \ 240 | -e VMAILGID=`id -g` \ 241 | -e DISABLE_CLAMAV=true \ 242 | -e TESTING=true \ 243 | -v "`pwd`/test/share/traefik/acme.v1.json":/etc/letsencrypt/acme/acme.json \ 244 | -h mail.domain.tld \ 245 | -t $(NAME) 246 | 247 | docker run \ 248 | -d \ 249 | --name mailserver_traefik_acmev2 \ 250 | --link mariadb:mariadb \ 251 | --link redis:redis \ 252 | -e DEBUG_MODE=true \ 253 | -e DBPASS=testpasswd \ 254 | -e RSPAMD_PASSWORD=testpasswd \ 255 | -e VMAILUID=`id -u` \ 256 | -e VMAILGID=`id -g` \ 257 | -e DISABLE_CLAMAV=true \ 258 | -e TESTING=true \ 259 | -v "`pwd`/test/share/traefik/acme.v2.json":/etc/letsencrypt/acme/acme.json \ 260 | -h mail.domain.tld \ 261 | -t $(NAME) 262 | 263 | fixtures: 264 | 265 | # Wait for clamav unofficial sigs database update (default) 266 | docker exec mailserver_default /bin/sh -c "while [ -f /var/lib/clamav-unofficial-sigs/pid/clamav-unofficial-sigs.pid ] ; do sleep 1 ; done" 267 | # Wait for clamav load databases (default) 268 | docker exec mailserver_default /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 3310 ; do sleep 1 ; done" 269 | 270 | # Wait for clamav unofficial sigs database update (ldap) 271 | docker exec mailserver_ldap /bin/sh -c "while [ -f /var/lib/clamav-unofficial-sigs/pid/clamav-unofficial-sigs.pid ] ; do sleep 1 ; done" 272 | # Wait for clamav load databases (ldap) 273 | docker exec mailserver_ldap /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 3310 ; do sleep 1 ; done" 274 | 275 | docker exec mailserver_default /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-user.txt" 276 | docker exec mailserver_default /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-user-spam-learning.txt" 277 | docker exec mailserver_default /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-valid-user-subaddress.txt" 278 | docker exec mailserver_default /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-non-existing-user.txt" 279 | docker exec mailserver_default /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-alias.txt" 280 | docker exec mailserver_default /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-spam-to-existing-user.txt" 281 | docker exec mailserver_default /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-virus-to-existing-user.txt" 282 | docker exec mailserver_default /bin/sh -c "openssl s_client -ign_eof -connect 0.0.0.0:587 -starttls smtp < /tmp/tests/email-templates/internal-user-to-existing-user.txt" 283 | docker exec mailserver_default /bin/sh -c "openssl s_client -ign_eof -connect 0.0.0.0:587 -starttls smtp < /tmp/tests/email-templates/internal-rejected-user-to-existing-user.txt" 284 | 285 | docker exec mailserver_reverse /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-user.txt" 286 | docker exec mailserver_reverse /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-valid-user-subaddress-with-default-separator.txt" 287 | docker exec mailserver_reverse /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-non-existing-user.txt" 288 | docker exec mailserver_reverse /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-alias.txt" 289 | docker exec mailserver_reverse /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-spam-to-existing-user.txt" 290 | docker exec mailserver_reverse /bin/sh -c "openssl s_client -ign_eof -connect 0.0.0.0:587 -starttls smtp < /tmp/tests/email-templates/internal-user-to-existing-user.txt" 291 | 292 | docker exec mailserver_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-user.txt" 293 | docker exec mailserver_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-user-spam-learning.txt" 294 | docker exec mailserver_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-valid-user-subaddress.txt" 295 | docker exec mailserver_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-non-existing-user.txt" 296 | docker exec mailserver_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-alias.txt" 297 | docker exec mailserver_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-alias-forward.txt" 298 | docker exec mailserver_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-alias-group.txt" 299 | docker exec mailserver_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-spam-to-existing-user.txt" 300 | docker exec mailserver_ldap /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-virus-to-existing-user.txt" 301 | docker exec mailserver_ldap /bin/sh -c "openssl s_client -ign_eof -connect 0.0.0.0:587 -starttls smtp < /tmp/tests/email-templates/internal-user-to-existing-user.txt" 302 | docker exec mailserver_ldap /bin/sh -c "openssl s_client -ign_eof -connect 0.0.0.0:587 -starttls smtp < /tmp/tests/email-templates/internal-rejected-user-to-existing-user.txt" 303 | 304 | docker exec mailserver_ldap2 /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-user.txt" 305 | docker exec mailserver_ldap2 /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-valid-user-subaddress.txt" 306 | docker exec mailserver_ldap2 /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-non-existing-user.txt" 307 | docker exec mailserver_ldap2 /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-alias.txt" 308 | docker exec mailserver_ldap2 /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-alias-forward.txt" 309 | docker exec mailserver_ldap2 /bin/sh -c "nc 0.0.0.0 25 < /tmp/tests/email-templates/external-to-existing-alias-group.txt" 310 | docker exec mailserver_ldap2 /bin/sh -c "openssl s_client -ign_eof -connect 0.0.0.0:587 -starttls smtp < /tmp/tests/email-templates/internal-user-to-existing-user.txt" 311 | 312 | sleep 2 313 | docker exec mailserver_default /bin/sh -c "openssl s_client -ign_eof -connect 0.0.0.0:993 < /tmp/tests/sieve/trigger-spam-ham-learning.txt" 314 | docker exec mailserver_ldap /bin/sh -c "openssl s_client -ign_eof -connect 0.0.0.0:993 < /tmp/tests/sieve/trigger-spam-ham-learning.txt" 315 | 316 | # Wait until all mails have been processed 317 | sleep 10 318 | 319 | run: 320 | ./test/bats/bin/bats test/tests.bats 321 | 322 | clean: 323 | docker images --quiet --filter=dangling=true | xargs --no-run-if-empty docker rmi 324 | docker volume ls -qf dangling=true | xargs -r docker volume rm 325 | -------------------------------------------------------------------------------- /docker-compose.sample.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | # IPv4 only 4 | # docker network create http_network 5 | 6 | # IPv4/IPv6 network 7 | # docker network create http_network --ipv6 --subnet "fd00:0000:0000:0000::/64" 8 | # Refer to https://github.com/hardware/mailserver/#ipv6-support for more information. 9 | 10 | networks: 11 | http_network: 12 | external: true 13 | mail_network: 14 | external: false 15 | 16 | services: 17 | traefik: 18 | image: traefik:1.7-alpine 19 | restart: ${RESTART_MODE} 20 | ports: 21 | - "80:80" 22 | - "443:443" 23 | labels: 24 | - traefik.enable=true 25 | - traefik.frontend.rule=Host:mail.${MAILSERVER_DOMAIN} 26 | - traefik.port=8080 27 | - traefik.docker.network=http_network 28 | volumes: 29 | - /var/run/docker.sock:/var/run/docker.sock:ro 30 | - ${VOLUMES_ROOT_PATH}/traefik/traefik.toml:/traefik.toml:ro 31 | - ${VOLUMES_ROOT_PATH}/traefik/acme:/etc/traefik/acme 32 | networks: 33 | - http_network 34 | 35 | mailserver: 36 | image: hardware/mailserver:${MAILSERVER_DOCKER_TAG} 37 | restart: ${RESTART_MODE} 38 | domainname: ${MAILSERVER_DOMAIN} # Mail server A/MX/FQDN & reverse PTR = mail.domain.tld. 39 | hostname: ${MAILSERVER_HOSTNAME} 40 | labels: 41 | - traefik.enable=true 42 | - traefik.frontend.rule=Host:spam.${MAILSERVER_DOMAIN} 43 | - traefik.port=11334 44 | - traefik.docker.network=http_network 45 | # extra_hosts: - Required for external database (on other server or for local databases on host without docker) 46 | # - "mariadb:xx.xx.xx.xx" - Replace with IP address of MariaDB server 47 | # - "redis:xx.xx.xx.xx" - Replace with IP address of Redis server 48 | ports: 49 | - "25:25" # SMTP - Required 50 | # - "110:110" # POP3 STARTTLS - Optional - For webmails/desktop clients 51 | - "143:143" # IMAP STARTTLS - Optional - For webmails/desktop clients 52 | # - "465:465" # SMTPS SSL/TLS - Optional - Enabled for compatibility reason, otherwise disabled 53 | - "587:587" # Submission STARTTLS - Optional - For webmails/desktop clients 54 | - "993:993" # IMAPS SSL/TLS - Optional - For webmails/desktop clients 55 | # - "995:995" # POP3S SSL/TLS - Optional - For webmails/desktop clients 56 | - "4190:4190" # SIEVE STARTTLS - Optional - Recommended for mail filtering 57 | environment: 58 | - DBPASS=${DATABASE_USER_PASSWORD} # MariaDB database password (required) 59 | - RSPAMD_PASSWORD=${RSPAMD_PASSWORD} # Rspamd WebUI password (required) 60 | # - ADD_DOMAINS=aa.tld, www.bb.tld... # Add additional domains separated by commas (needed for dkim keys etc.) 61 | # - DEBUG_MODE=true # Enable Postfix, Dovecot, Rspamd and Unbound verbose logging 62 | # - ENABLE_POP3=true # Enable POP3 protocol 63 | # - ENABLE_FETCHMAIL=true # Enable fetchmail forwarding 64 | # - DISABLE_RATELIMITING=false # Enable ratelimiting policy 65 | # - DISABLE_CLAMAV=true # Disable virus scanning 66 | # - DISABLE_SIGNING=true # Disable DKIM/ARC signing 67 | # - DISABLE_GREYLISTING=true # Disable greylisting policy 68 | # 69 | # Full list : https://github.com/hardware/mailserver#environment-variables 70 | # 71 | volumes: 72 | - ${VOLUMES_ROOT_PATH}/mail:/var/mail 73 | depends_on: 74 | - mariadb 75 | - redis 76 | networks: 77 | - mail_network 78 | - http_network 79 | 80 | # Administration interface 81 | # https://github.com/hardware/postfixadmin 82 | # http://postfixadmin.sourceforge.net/ 83 | # Configuration : https://github.com/hardware/mailserver/wiki/Postfixadmin-initial-configuration 84 | postfixadmin: 85 | image: hardware/postfixadmin 86 | restart: ${RESTART_MODE} 87 | domainname: ${MAILSERVER_DOMAIN} 88 | hostname: ${MAILSERVER_HOSTNAME} 89 | labels: 90 | - traefik.enable=true 91 | - traefik.frontend.rule=Host:postfixadmin.${MAILSERVER_DOMAIN} 92 | - traefik.port=8888 93 | - traefik.docker.network=http_network 94 | environment: 95 | - DBPASS=${DATABASE_USER_PASSWORD} 96 | depends_on: 97 | - mailserver 98 | - mariadb 99 | networks: 100 | - mail_network 101 | - http_network 102 | 103 | # Webmail (Optional) 104 | # https://github.com/hardware/rainloop 105 | # https://www.rainloop.net/ 106 | # Configuration : https://github.com/hardware/mailserver/wiki/Rainloop-initial-configuration 107 | rainloop: 108 | image: hardware/rainloop 109 | restart: ${RESTART_MODE} 110 | labels: 111 | - traefik.enable=true 112 | - traefik.port=8888 113 | - traefik.frontend.rule=Host:webmail.${MAILSERVER_DOMAIN} 114 | - traefik.docker.network=http_network 115 | volumes: 116 | - ${VOLUMES_ROOT_PATH}/rainloop:/rainloop/data 117 | depends_on: 118 | - mailserver 119 | - mariadb 120 | networks: 121 | - mail_network 122 | - http_network 123 | 124 | # Authoritative DNS server (Optional) 125 | # https://github.com/hardware/nsd-dnssec 126 | # https://www.nlnetlabs.nl/projects/nsd/ 127 | # Configuration : https://github.com/hardware/mailserver/wiki/NSD-initial-configuration 128 | # nsd: 129 | # image: hardware/nsd-dnssec 130 | # restart: ${RESTART_MODE} 131 | # ports: 132 | # - "53:53" 133 | # - "53:53/udp" 134 | # volumes: 135 | # - ${VOLUMES_ROOT_PATH}/nsd/conf:/etc/nsd 136 | # - ${VOLUMES_ROOT_PATH}/nsd/zones:/zones 137 | # - ${VOLUMES_ROOT_PATH}/nsd/db:/var/db/nsd 138 | 139 | # Database 140 | # https://github.com/docker-library/mariadb 141 | # https://mariadb.org/ 142 | mariadb: 143 | image: mariadb:10.2 144 | restart: ${RESTART_MODE} 145 | # Info : These variables are ignored when the volume already exists (if databases was created before). 146 | environment: 147 | - MYSQL_RANDOM_ROOT_PASSWORD=yes 148 | - MYSQL_DATABASE=postfix 149 | - MYSQL_USER=postfix 150 | - MYSQL_PASSWORD=${DATABASE_USER_PASSWORD} 151 | volumes: 152 | - ${VOLUMES_ROOT_PATH}/mysql/db:/var/lib/mysql 153 | networks: 154 | - mail_network 155 | 156 | # Database 157 | # https://github.com/docker-library/redis 158 | # https://redis.io/ 159 | redis: 160 | image: redis:4.0-alpine 161 | restart: ${RESTART_MODE} 162 | command: redis-server --appendonly yes 163 | volumes: 164 | - ${VOLUMES_ROOT_PATH}/redis/db:/data 165 | networks: 166 | - mail_network 167 | -------------------------------------------------------------------------------- /rootfs/etc/clamav/unofficial-sigs/os.conf: -------------------------------------------------------------------------------- 1 | clam_user="clamav" 2 | clam_group="clamav" 3 | logrotate_group="adm" 4 | clam_dbs="/var/lib/clamav" 5 | clamd_pid="/run/clamav/clamd.pid" 6 | clamd_socket="/run/clamav/clamd.ctl" 7 | clamd_reload_opt="s6-svc -2 /services/clamd" 8 | min_sleep_time="30" 9 | max_sleep_time="60" 10 | -------------------------------------------------------------------------------- /rootfs/etc/cron.d/counters: -------------------------------------------------------------------------------- 1 | 00 00 * * * root echo 0 > /tmp/counters/_parent 2 | 00 00 * * * root echo 0 > /tmp/counters/clamd 3 | 00 00 * * * root echo 0 > /tmp/counters/cron 4 | 00 00 * * * root echo 0 > /tmp/counters/dovecot 5 | 00 00 * * * root echo 0 > /tmp/counters/freshclam 6 | 00 00 * * * root echo 0 > /tmp/counters/postfix 7 | 00 00 * * * root echo 0 > /tmp/counters/rspamd 8 | 00 00 * * * root echo 0 > /tmp/counters/rsyslogd 9 | 00 00 * * * root echo 0 > /tmp/counters/unbound 10 | 00 00 * * * root echo 0 > /tmp/counters/cert_watcher 11 | -------------------------------------------------------------------------------- /rootfs/etc/cron.d/fetchmail: -------------------------------------------------------------------------------- 1 | */{{ .FETCHMAIL_INTERVAL }} * * * * root /usr/bin/perl /usr/local/bin/fetchmail.pl > /dev/null 2>&1 2 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/10-auth.conf: -------------------------------------------------------------------------------- 1 | disable_plaintext_auth = yes 2 | auth_mechanisms = plain login 3 | 4 | {{ if eq .DBDRIVER "ldap" }} 5 | !include auth-ldap.conf.ext 6 | {{ else }} 7 | !include auth-sql.conf.ext 8 | {{ end }} 9 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/10-logging.conf: -------------------------------------------------------------------------------- 1 | log_path = syslog 2 | syslog_facility = mail 3 | 4 | #auth_verbose = yes 5 | #auth_verbose_passwords = sha1 6 | #auth_debug = yes 7 | #auth_debug_passwords = yes 8 | #mail_debug = yes 9 | #verbose_ssl = yes 10 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/10-mail.conf: -------------------------------------------------------------------------------- 1 | mail_plugins = $mail_plugins quota 2 | mail_location = maildir:/var/mail/vhosts/%d/%n/{{ .VMAIL_SUBDIR }} 3 | maildir_stat_dirs=yes 4 | 5 | namespace inbox { 6 | inbox = yes 7 | } 8 | 9 | mail_uid = {{ .VMAILUID }} 10 | mail_gid = {{ .VMAILGID }} 11 | 12 | first_valid_uid = {{ .VMAILUID }} 13 | last_valid_uid = {{ .VMAILUID }} 14 | 15 | mail_privileged_group = vmail 16 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/10-master.conf: -------------------------------------------------------------------------------- 1 | service imap-login { 2 | 3 | inet_listener imap { 4 | port = 143 5 | } 6 | 7 | inet_listener imaps { 8 | port = 993 9 | ssl = yes 10 | } 11 | 12 | service_count = 1 13 | process_min_avail = DOVECOT_MIN_PROCESS 14 | process_limit = DOVECOT_MAX_PROCESS 15 | 16 | } 17 | 18 | service imap { 19 | 20 | process_min_avail = DOVECOT_MIN_PROCESS 21 | process_limit = DOVECOT_MAX_PROCESS 22 | vsz_limit = 512M # For large mailboxes 23 | 24 | } 25 | 26 | service pop3-login { 27 | 28 | inet_listener pop3 { 29 | port = 110 30 | } 31 | 32 | inet_listener pop3s { 33 | port = 995 34 | ssl = yes 35 | } 36 | 37 | service_count = 1 38 | process_min_avail = DOVECOT_MIN_PROCESS 39 | process_limit = DOVECOT_MAX_PROCESS 40 | 41 | } 42 | 43 | service pop3 { 44 | 45 | process_min_avail = DOVECOT_MIN_PROCESS 46 | process_limit = DOVECOT_MAX_PROCESS 47 | vsz_limit = 512M # For large mailboxes 48 | 49 | } 50 | 51 | service lmtp { 52 | 53 | unix_listener /var/mail/postfix/spool/private/dovecot-lmtp { 54 | mode = 0600 55 | user = postfix 56 | group = postfix 57 | } 58 | 59 | } 60 | 61 | service auth { 62 | 63 | unix_listener /var/mail/postfix/spool/private/auth { 64 | mode = 0666 65 | user = postfix 66 | group = postfix 67 | } 68 | 69 | unix_listener auth-userdb { 70 | mode = 0600 71 | user = vmail 72 | group = vmail 73 | } 74 | 75 | user = dovecot 76 | 77 | } 78 | 79 | service auth-worker { 80 | 81 | user = vmail 82 | 83 | } 84 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/10-ssl.conf: -------------------------------------------------------------------------------- 1 | ssl = required 2 | ssl_cert = <{{ .FULLCHAIN }} 3 | ssl_key = <{{ .KEYFILE }} 4 | ssl_min_protocol = TLSv1.2 5 | ssl_cipher_list = EECDH+AES:EDH+AES+aRSA:!DH # PFS only and disallow non-ECC based DH algorithms 6 | ssl_prefer_server_ciphers = yes 7 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/15-lda.conf: -------------------------------------------------------------------------------- 1 | recipient_delimiter = {{ .RECIPIENT_DELIMITER }} 2 | 3 | protocol lda { 4 | 5 | postmaster_address = postmaster@{{ .DOMAIN }} 6 | mail_plugins = $mail_plugins 7 | 8 | } 9 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/15-mailboxes.conf: -------------------------------------------------------------------------------- 1 | ## 2 | ## Mailbox definitions 3 | ## 4 | 5 | # NOTE: Assumes "namespace inbox" has been defined in 10-mail.conf. 6 | # 7 | # auto=create will automatically create this mailbox. 8 | # auto=subscribe will both create and subscribe to the mailbox. 9 | # 10 | # Space separated list of IMAP SPECIAL-USE attributes as specified by 11 | # RFC 6154: \All \Archive \Drafts \Flagged \Junk \Sent \Trash 12 | 13 | namespace inbox { 14 | 15 | mailbox Drafts { 16 | special_use = \Drafts 17 | auto = subscribe 18 | } 19 | 20 | mailbox Spam { 21 | special_use = \Junk 22 | auto = subscribe 23 | } 24 | 25 | mailbox Junk { 26 | special_use = \Junk 27 | } 28 | 29 | mailbox Trash { 30 | special_use = \Trash 31 | auto = subscribe 32 | } 33 | 34 | mailbox Sent { 35 | special_use = \Sent 36 | auto = subscribe 37 | } 38 | 39 | mailbox "Sent Messages" { 40 | special_use = \Sent 41 | } 42 | 43 | mailbox Archive { 44 | special_use = \Archive 45 | auto = subscribe 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/20-imap.conf: -------------------------------------------------------------------------------- 1 | imap_idle_notify_interval = 4 mins 2 | 3 | protocol imap { 4 | 5 | mail_plugins = $mail_plugins imap_quota imap_sieve 6 | imap_client_workarounds = tb-extra-mailbox-sep 7 | mail_max_userip_connections = 20 8 | 9 | } 10 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/20-lmtp.conf: -------------------------------------------------------------------------------- 1 | protocol lmtp { 2 | 3 | postmaster_address = postmaster@{{ .DOMAIN }} 4 | mail_plugins = $mail_plugins sieve 5 | 6 | } 7 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/20-pop3.conf: -------------------------------------------------------------------------------- 1 | protocol pop3 { 2 | 3 | mail_plugins = $mail_plugins 4 | pop3_client_workarounds = outlook-no-nuls oe-ns-eoh 5 | pop3_uidl_format = %08Xu%08Xv 6 | mail_max_userip_connections = 20 7 | 8 | } 9 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/90-quota.conf: -------------------------------------------------------------------------------- 1 | plugin { 2 | {{ if ne .DBDRIVER "ldap" }} 3 | quota = dict:User quota::proxy::sqlquota 4 | {{ else }} 5 | quota = maildir 6 | {{ end }} 7 | quota_rule = *:storage=5GB 8 | quota_rule2 = Trash:storage=+100M 9 | quota_grace = 10%% 10 | quota_exceeded_message = The quota of this mailbox is exhausted. Contact your system administrator. 11 | quota_warning = storage=90%% quota-warning 90 %u 12 | quota_warning2 = storage=80%% quota-warning 80 %u 13 | quota_warning3 = storage=70%% quota-warning 70 %u 14 | quota_warning4 = storage=60%% quota-warning 60 %u 15 | 16 | } 17 | 18 | {{ if ne .DBDRIVER "ldap" }} 19 | dict { 20 | sqlquota = {{ .DBDRIVER }}:/etc/dovecot/dovecot-dict-sql.conf.ext 21 | } 22 | {{ end }} 23 | 24 | service dict { 25 | 26 | unix_listener dict { 27 | mode = 0600 28 | user = vmail 29 | group = vmail 30 | } 31 | 32 | } 33 | 34 | service quota-warning { 35 | 36 | executable = script /usr/local/bin/quota-warning.sh 37 | user = vmail 38 | 39 | unix_listener quota-warning { 40 | user = vmail 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/90-sieve.conf: -------------------------------------------------------------------------------- 1 | plugin { 2 | 3 | sieve_plugins = sieve_imapsieve sieve_extprograms 4 | 5 | sieve = /var/mail/vhosts/%d/%n/.dovecot.sieve 6 | sieve_default = /var/mail/sieve/default.sieve 7 | sieve_after = /var/mail/sieve/default.sieve 8 | sieve_dir = /var/mail/vhosts/%d/%n/sieve 9 | sieve_global = /var/mail/sieve 10 | sieve_max_script_size = 1M 11 | sieve_quota_max_scripts = 0 12 | sieve_quota_max_storage = 0 13 | sieve_pipe_bin_dir = /etc/dovecot/sieve 14 | sieve_global_extensions = +vnd.dovecot.pipe 15 | 16 | # Pigeonhole Sieve Vacation Extension fix 17 | # -------------------------------------------------------------- 18 | # More info : 19 | # https://github.com/hardware/mailserver/issues/227 20 | # http://wiki2.dovecot.org/Pigeonhole/Sieve/Extensions/Vacation 21 | # https://tools.ietf.org/html/rfc5230#section-4.5 22 | 23 | # Set the enveloppe address to allow rspamd to guess which 24 | # domain key the message should be signed with 25 | sieve_vacation_send_from_recipient = yes 26 | 27 | # Send vacation replies even for aliases 28 | sieve_vacation_dont_check_recipient = yes 29 | # -------------------------------------------------------------- 30 | 31 | # From elsewhere to Spam folder 32 | imapsieve_mailbox1_name = Spam 33 | imapsieve_mailbox1_causes = COPY 34 | imapsieve_mailbox1_before = file:/etc/dovecot/sieve/report-spam.sieve 35 | 36 | # From Spam folder to elsewhere 37 | imapsieve_mailbox2_name = * 38 | imapsieve_mailbox2_from = Spam 39 | imapsieve_mailbox2_causes = COPY 40 | imapsieve_mailbox2_before = file:/etc/dovecot/sieve/report-ham.sieve 41 | 42 | } 43 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/auth-ldap.conf.ext: -------------------------------------------------------------------------------- 1 | {{ if eq .LDAP_MASTER_USER_ENABLED "true" }} 2 | auth_master_user_separator = {{ .LDAP_MASTER_USER_SEPARATOR }} 3 | passdb { 4 | driver = ldap 5 | args = /etc/dovecot/dovecot-ldap-master.conf.ext 6 | master = yes 7 | pass = yes 8 | } 9 | {{ end }} 10 | passdb { 11 | driver = ldap 12 | args = /etc/dovecot/dovecot-ldap.conf.ext 13 | } 14 | 15 | userdb { 16 | driver = ldap 17 | args = /etc/dovecot/dovecot-ldap.conf.ext 18 | } 19 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/conf.d/auth-sql.conf.ext: -------------------------------------------------------------------------------- 1 | passdb { 2 | driver = sql 3 | args = /etc/dovecot/dovecot-sql.conf.ext 4 | } 5 | 6 | userdb { 7 | driver = sql 8 | args = /etc/dovecot/dovecot-sql.conf.ext 9 | } 10 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/dovecot-dict-sql.conf.ext: -------------------------------------------------------------------------------- 1 | connect = "host={{ .DBHOST }} port={{ .DBPORT }} dbname={{ .DBNAME }} user={{ .DBUSER }} password={{ .DBPASS }}" 2 | 3 | map { 4 | pattern = priv/quota/storage 5 | table = quota2 6 | username_field = username 7 | value_field = bytes 8 | } 9 | 10 | map { 11 | pattern = priv/quota/messages 12 | table = quota2 13 | username_field = username 14 | value_field = messages 15 | } 16 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/dovecot-ldap-master.conf.ext: -------------------------------------------------------------------------------- 1 | hosts = {{ .DBHOST }}:{{ .DBPORT }} 2 | ldap_version = 3 3 | {{ if eq .LDAP_BIND "true" }} 4 | auth_bind = yes 5 | dn = {{ .LDAP_BIND_DN }} 6 | dnpass = {{ .LDAP_BIND_PW }} 7 | {{ else }} 8 | auth_bind = no 9 | {{ end }} 10 | base = {{ .LDAP_MASTER_USER_SEARCH_BASE }} 11 | {{ if eq .LDAP_MASTER_USER_SEARCH_SCOPE "sub" }} 12 | scope = subtree 13 | {{ else if eq .LDAP_MASTER_USER_SEARCH_SCOPE "base" }} 14 | scope = base 15 | {{ else if eq .LDAP_MASTER_USER_SEARCH_SCOPE "one" }} 16 | scope = onelevel 17 | {{ end }} 18 | deref = never 19 | 20 | pass_attrs = {{ .LDAP_DOVECOT_MASTER_PASS_ATTRS }} 21 | pass_filter = {{ .LDAP_DOVECOT_MASTER_PASS_FILTER }} 22 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/dovecot-ldap.conf.ext: -------------------------------------------------------------------------------- 1 | hosts = {{ .DBHOST }}:{{ .DBPORT }} 2 | ldap_version = 3 3 | {{ if eq .LDAP_BIND "true" }} 4 | auth_bind = yes 5 | dn = {{ .LDAP_BIND_DN }} 6 | dnpass = {{ .LDAP_BIND_PW }} 7 | {{ else }} 8 | auth_bind = no 9 | {{ end }} 10 | base = {{ .LDAP_MAILBOX_SEARCH_BASE }} 11 | {{ if eq .LDAP_MAILBOX_SEARCH_SCOPE "sub" }} 12 | scope = subtree 13 | {{ else if eq .LDAP_MAILBOX_SEARCH_SCOPE "base" }} 14 | scope = base 15 | {{ else if eq .LDAP_MAILBOX_SEARCH_SCOPE "one" }} 16 | scope = onelevel 17 | {{ end }} 18 | deref = never 19 | 20 | user_attrs = {{ .LDAP_DOVECOT_USER_ATTRS }} 21 | user_filter = {{ .LDAP_DOVECOT_USER_FILTER }} 22 | 23 | pass_attrs = {{ .LDAP_DOVECOT_PASS_ATTRS }} 24 | pass_filter = {{ .LDAP_DOVECOT_PASS_FILTER }} 25 | 26 | {{ if ne .LDAP_DOVECOT_ITERATE_ATTRS "" }} 27 | iterate_attrs = {{ .LDAP_DOVECOT_ITERATE_ATTRS }} 28 | {{ end }} 29 | {{ if ne .LDAP_DOVECOT_ITERATE_FILTER "" }} 30 | iterate_filter = {{ .LDAP_DOVECOT_ITERATE_FILTER }} 31 | {{ end }} 32 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/dovecot-sql.conf.ext: -------------------------------------------------------------------------------- 1 | driver = {{ .DBDRIVER }} 2 | connect = "host={{ .DBHOST }} port={{ .DBPORT }} dbname={{ .DBNAME }} user={{ .DBUSER }} password={{ .DBPASS }}" 3 | default_pass_scheme = {{ .PASSWORD_SCHEME }} 4 | iterate_query = SELECT username AS user FROM mailbox 5 | user_query = SELECT CONCAT('/var/mail/vhosts/',maildir) AS home, CONCAT('maildir:/var/mail/vhosts/',maildir,'{{ .VMAIL_SUBDIR }}') AS mail, {{ .VMAILUID }} AS uid, {{ .VMAILGID }} AS gid, CONCAT('*:bytes=',quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = TRUE 6 | password_query = SELECT password, CONCAT('*:bytes=',quota) AS userdb_quota_rule FROM mailbox WHERE username = '%u' AND active = TRUE 7 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/dovecot.conf: -------------------------------------------------------------------------------- 1 | !include_try /usr/share/dovecot/protocols.d/*.protocol 2 | protocols = imap lmtp 3 | listen = *, [::] 4 | !include conf.d/*.conf 5 | !include_try /var/mail/dovecot/conf.d/*.conf 6 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/sieve/report-ham.sieve: -------------------------------------------------------------------------------- 1 | require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; 2 | 3 | if environment :matches "imap.mailbox" "*" { 4 | set "mailbox" "${1}"; 5 | } 6 | 7 | if string "${mailbox}" "Trash" { 8 | stop; 9 | } 10 | 11 | pipe :copy "rspamd-pipe-ham.sh"; 12 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/sieve/report-spam.sieve: -------------------------------------------------------------------------------- 1 | require ["vnd.dovecot.pipe", "copy"]; 2 | 3 | pipe :copy "rspamd-pipe-spam.sh"; 4 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/sieve/rspamd-pipe-ham.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # rspamd client reads piped ham message from the standard input 4 | exec /usr/bin/rspamc learn_ham 5 | -------------------------------------------------------------------------------- /rootfs/etc/dovecot/sieve/rspamd-pipe-spam.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # rspamd client reads piped spam message from the standard input 4 | exec /usr/bin/rspamc learn_spam 5 | -------------------------------------------------------------------------------- /rootfs/etc/mailname: -------------------------------------------------------------------------------- 1 | {{ .FQDN }} 2 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/ffdhe2048.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz 3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD 7 | ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== 8 | -----END DH PARAMETERS----- 9 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/header_checks: -------------------------------------------------------------------------------- 1 | # Based on implementation of @JoshData (initially by @mkropat) for Mail-in-a-Box Project 2 | # https://github.com/mail-in-a-box/mailinabox 3 | 4 | # Remove the first line of the Received: header. Note that we cannot fully remove the Received: header 5 | # because OpenDKIM requires that a header be present when signing outbound mail. The first line is 6 | # where the user's home IP address would be. 7 | /^\s*Received:[^\n]*(.*)/ REPLACE Received: from authenticated-user ({{ .FQDN }} [127.0.0.1])$1 8 | 9 | # Remove other typically private information. 10 | /^\s*User-Agent:/ IGNORE 11 | /^\s*X-Enigmail:/ IGNORE 12 | /^\s*X-Mailer:/ IGNORE 13 | /^\s*X-Originating-IP:/ IGNORE 14 | /^\s*X-Pgp-Agent:/ IGNORE 15 | 16 | # The Mime-Version header can leak the user agent too, e.g. in Mime-Version: 1.0 (Mac OS X Mail 8.1 \(2010.6\)). 17 | /^\s*(Mime-Version:\s*[0-9\.]+)\s.+/ REPLACE $1 18 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/ldap/sender-login-maps.cf: -------------------------------------------------------------------------------- 1 | server_host = {{ .DBHOST }}:{{ .DBPORT }} 2 | version = 3 3 | 4 | {{ if eq .LDAP_TLS_ENABLED "true" }} 5 | start_tls = yes 6 | {{ else }} 7 | start_tls = no 8 | {{ end }} 9 | 10 | {{ if ne .LDAP_TLS_CA_FILE "" }} 11 | tls_ca_cert_file = {{ .LDAP_TLS_CA_FILE }} 12 | {{ end }} 13 | 14 | {{ if eq .LDAP_TLS_FORCE "true" }} 15 | tls_require_cert = yes 16 | {{ end }} 17 | 18 | {{ if eq .LDAP_BIND "true" }} 19 | bind = yes 20 | bind_dn = {{ .LDAP_BIND_DN }} 21 | bind_pw = {{ .LDAP_BIND_PW }} 22 | {{ else }} 23 | bind = no 24 | {{ end }} 25 | 26 | 27 | search_base = {{ .LDAP_SENDER_SEARCH_BASE }} 28 | scope = {{ .LDAP_SENDER_SEARCH_SCOPE }} 29 | 30 | query_filter = {{ .LDAP_SENDER_FILTER }} 31 | result_attribute = {{ .LDAP_SENDER_ATTRIBUTE }} 32 | 33 | {{ if ne .LDAP_SENDER_FORMAT "" }} 34 | result_format = {{ .LDAP_SENDER_FORMAT }} 35 | {{ end }} 36 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/ldap/virtual-alias-maps.cf: -------------------------------------------------------------------------------- 1 | server_host = {{ .DBHOST }}:{{ .DBPORT }} 2 | version = 3 3 | 4 | {{ if eq .LDAP_TLS_ENABLED "true" }} 5 | start_tls = yes 6 | {{ else }} 7 | start_tls = no 8 | {{ end }} 9 | 10 | {{ if ne .LDAP_TLS_CA_FILE "" }} 11 | tls_ca_cert_file = {{ .LDAP_TLS_CA_FILE }} 12 | {{ end }} 13 | 14 | {{ if eq .LDAP_TLS_FORCE "true" }} 15 | tls_require_cert = yes 16 | {{ end }} 17 | 18 | {{ if eq .LDAP_BIND "true" }} 19 | bind = yes 20 | bind_dn = {{ .LDAP_BIND_DN }} 21 | bind_pw = {{ .LDAP_BIND_PW }} 22 | {{ else }} 23 | bind = no 24 | {{ end }} 25 | 26 | 27 | search_base = {{ .LDAP_ALIAS_SEARCH_BASE }} 28 | scope = {{ .LDAP_ALIAS_SEARCH_SCOPE }} 29 | 30 | query_filter = {{ .LDAP_ALIAS_FILTER }} 31 | result_attribute = {{ .LDAP_ALIAS_ATTRIBUTE }} 32 | 33 | {{ if ne .LDAP_ALIAS_FORMAT "" }} 34 | result_format = {{ .LDAP_ALIAS_FORMAT }} 35 | {{ end }} 36 | 37 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/ldap/virtual-forward-maps.cf: -------------------------------------------------------------------------------- 1 | server_host = {{ .DBHOST }}:{{ .DBPORT }} 2 | version = 3 3 | 4 | {{ if eq .LDAP_TLS_ENABLED "true" }} 5 | start_tls = yes 6 | {{ else }} 7 | start_tls = no 8 | {{ end }} 9 | 10 | {{ if ne .LDAP_TLS_CA_FILE "" }} 11 | tls_ca_cert_file = {{ .LDAP_TLS_CA_FILE }} 12 | {{ end }} 13 | 14 | {{ if eq .LDAP_TLS_FORCE "true" }} 15 | tls_require_cert = yes 16 | {{ end }} 17 | 18 | {{ if eq .LDAP_BIND "true" }} 19 | bind = yes 20 | bind_dn = {{ .LDAP_BIND_DN }} 21 | bind_pw = {{ .LDAP_BIND_PW }} 22 | {{ else }} 23 | bind = no 24 | {{ end }} 25 | 26 | 27 | search_base = {{ .LDAP_FORWARD_SEARCH_BASE }} 28 | scope = {{ .LDAP_FORWARD_SEARCH_SCOPE }} 29 | 30 | query_filter = {{ .LDAP_FORWARD_FILTER }} 31 | result_attribute = {{ .LDAP_FORWARD_ATTRIBUTE }} 32 | 33 | {{ if ne .LDAP_FORWARD_FORMAT "" }} 34 | result_format = {{ .LDAP_FORWARD_FORMAT }} 35 | {{ end }} 36 | 37 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/ldap/virtual-group-maps.cf: -------------------------------------------------------------------------------- 1 | server_host = {{ .DBHOST }}:{{ .DBPORT }} 2 | version = 3 3 | 4 | {{ if eq .LDAP_TLS_ENABLED "true" }} 5 | start_tls = yes 6 | {{ else }} 7 | start_tls = no 8 | {{ end }} 9 | 10 | {{ if ne .LDAP_TLS_CA_FILE "" }} 11 | tls_ca_cert_file = {{ .LDAP_TLS_CA_FILE }} 12 | {{ end }} 13 | 14 | {{ if eq .LDAP_TLS_FORCE "true" }} 15 | tls_require_cert = yes 16 | {{ end }} 17 | 18 | {{ if eq .LDAP_BIND "true" }} 19 | bind = yes 20 | bind_dn = {{ .LDAP_BIND_DN }} 21 | bind_pw = {{ .LDAP_BIND_PW }} 22 | {{ else }} 23 | bind = no 24 | {{ end }} 25 | 26 | 27 | search_base = {{ .LDAP_GROUP_SEARCH_BASE }} 28 | scope = {{ .LDAP_GROUP_SEARCH_SCOPE }} 29 | 30 | query_filter = {{ .LDAP_GROUP_FILTER }} 31 | result_attribute = {{ .LDAP_GROUP_ATTRIBUTE }} 32 | 33 | {{ if ne .LDAP_GROUP_FORMAT "" }} 34 | result_format = {{ .LDAP_GROUP_FORMAT }} 35 | {{ end }} 36 | 37 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/ldap/virtual-mailbox-domains.cf: -------------------------------------------------------------------------------- 1 | server_host = {{ .DBHOST }}:{{ .DBPORT }} 2 | version = 3 3 | 4 | {{ if eq .LDAP_TLS_ENABLED "true" }} 5 | start_tls = yes 6 | {{ else }} 7 | start_tls = no 8 | {{ end }} 9 | 10 | {{ if ne .LDAP_TLS_CA_FILE "" }} 11 | tls_ca_cert_file = {{ .LDAP_TLS_CA_FILE }} 12 | {{ end }} 13 | 14 | {{ if eq .LDAP_TLS_FORCE "true" }} 15 | tls_require_cert = yes 16 | {{ end }} 17 | 18 | {{ if eq .LDAP_BIND "true" }} 19 | bind = yes 20 | bind_dn = {{ .LDAP_BIND_DN }} 21 | bind_pw = {{ .LDAP_BIND_PW }} 22 | {{ else }} 23 | bind = no 24 | {{ end }} 25 | 26 | 27 | search_base = {{ .LDAP_DOMAIN_SEARCH_BASE }} 28 | scope = {{ .LDAP_DOMAIN_SEARCH_SCOPE }} 29 | 30 | query_filter = {{ .LDAP_DOMAIN_FILTER }} 31 | result_attribute = {{ .LDAP_DOMAIN_ATTRIBUTE }} 32 | 33 | {{ if ne .LDAP_DOMAIN_FORMAT "" }} 34 | result_format = {{ .LDAP_DOMAIN_FORMAT }} 35 | {{ end }} 36 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/ldap/virtual-mailbox-maps.cf: -------------------------------------------------------------------------------- 1 | server_host = {{ .DBHOST }}:{{ .DBPORT }} 2 | version = 3 3 | 4 | {{ if eq .LDAP_TLS_ENABLED "true" }} 5 | start_tls = yes 6 | {{ else }} 7 | start_tls = no 8 | {{ end }} 9 | 10 | {{ if ne .LDAP_TLS_CA_FILE "" }} 11 | tls_ca_cert_file = {{ .LDAP_TLS_CA_FILE }} 12 | {{ end }} 13 | 14 | {{ if eq .LDAP_TLS_FORCE "true" }} 15 | tls_require_cert = yes 16 | {{ end }} 17 | 18 | {{ if eq .LDAP_BIND "true" }} 19 | bind = yes 20 | bind_dn = {{ .LDAP_BIND_DN }} 21 | bind_pw = {{ .LDAP_BIND_PW }} 22 | {{ else }} 23 | bind = no 24 | {{ end }} 25 | 26 | 27 | search_base = {{ .LDAP_MAILBOX_SEARCH_BASE }} 28 | scope = {{ .LDAP_MAILBOX_SEARCH_SCOPE }} 29 | 30 | query_filter = {{ .LDAP_MAILBOX_FILTER }} 31 | result_attribute = {{ .LDAP_MAILBOX_ATTRIBUTE }} 32 | 33 | {{ if ne .LDAP_MAILBOX_FORMAT "" }} 34 | result_format = {{ .LDAP_MAILBOX_FORMAT }} 35 | {{ end }} 36 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/main.cf: -------------------------------------------------------------------------------- 1 | ####################### 2 | ## GENERALS SETTINGS ## 3 | ####################### 4 | 5 | smtpd_banner = $myhostname ESMTP $mail_name 6 | compatibility_level = 2 7 | biff = no 8 | append_dot_mydomain = no 9 | readme_directory = no 10 | allow_percent_hack = no 11 | delay_warning_time = 4h 12 | mailbox_command = procmail -a "$EXTENSION" 13 | recipient_delimiter = {{ .RECIPIENT_DELIMITER }} 14 | disable_vrfy_command = yes 15 | message_size_limit = 502400000 16 | mailbox_size_limit = 1024000000 17 | 18 | inet_interfaces = all 19 | inet_protocols = all 20 | 21 | myhostname = {{ .FQDN }} 22 | myorigin = {{ .FQDN }} 23 | mydestination = localhost localhost.$mydomain 24 | mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 {{ .RELAY_NETWORKS }} 25 | 26 | alias_maps = hash:/etc/aliases 27 | 28 | ############### 29 | ## SMTP/UTF8 ## 30 | ############### 31 | 32 | smtputf8_enable = yes 33 | 34 | # The default is to enable "SMTPUTF8 required" autodetection 35 | # only for Postfix sendmail command-line submissions and address 36 | # verification probes. 37 | # https://github.com/hardware/mailserver/issues/166 38 | # smtputf8_autodetect_classes = all 39 | 40 | ################### 41 | ## RATE LIMITING ## 42 | ################### 43 | 44 | # Allow to avoid 421 error when send bulk mail 45 | default_destination_rate_delay = 1s 46 | default_destination_recipient_limit = 10 47 | 48 | # concurrency_limit has no effect when rate_delay is turned on. 49 | # It specifies a delay BETWEEN deliveries, meaning the deliveries 50 | # cannot be in parallel. 51 | # default_destination_concurrency_limit=2 52 | 53 | #################### 54 | ## TLS PARAMETERS ## 55 | #################### 56 | 57 | # Smtp ( OUTGOING ) 58 | smtp_tls_loglevel = 1 59 | smtp_tls_security_level = dane 60 | smtp_dns_support_level = dnssec 61 | smtp_tls_CApath = /etc/ssl/certs 62 | smtp_tls_protocols = !TLSv1, !SSLv2, !SSLv3 63 | smtp_tls_mandatory_protocols = !TLSv1, !SSLv2, !SSLv3 64 | smtp_tls_mandatory_ciphers = medium 65 | smtp_tls_note_starttls_offer = yes 66 | 67 | # Smtpd ( INCOMING ) 68 | smtpd_tls_loglevel = 1 69 | smtpd_tls_auth_only = yes 70 | smtpd_tls_security_level = may 71 | smtpd_tls_received_header = yes 72 | smtpd_tls_protocols = !TLSv1, !SSLv2, !SSLv3 73 | smtpd_tls_mandatory_protocols = !TLSv1, !SSLv2, !SSLv3 74 | smtpd_tls_mandatory_ciphers = medium 75 | smtpd_tls_exclude_ciphers = aNULL,eNULL,EXPORT,DES,3DES,RC2,RC4,MD5,PSK,SRP,DSS,AECDH,ADH,SEED 76 | smtpd_tls_CApath = /etc/ssl/certs 77 | smtpd_tls_cert_file = {{ .FULLCHAIN }} 78 | smtpd_tls_key_file = {{ .KEYFILE }} 79 | smtpd_tls_dh1024_param_file = /etc/postfix/ffdhe2048.pem 80 | 81 | tls_preempt_cipherlist = yes 82 | tls_random_source = dev:/dev/urandom 83 | 84 | smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache 85 | smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache 86 | lmtp_tls_session_cache_database = btree:${data_directory}/lmtp_scache 87 | 88 | ##################### 89 | ## SASL PARAMETERS ## 90 | ##################### 91 | 92 | smtpd_sasl_auth_enable = yes 93 | smtpd_sasl_type = dovecot 94 | smtpd_sasl_path = private/auth 95 | smtpd_sasl_security_options = noanonymous 96 | smtpd_sasl_tls_security_options = $smtpd_sasl_security_options 97 | smtpd_sasl_local_domain = $mydomain 98 | smtpd_sasl_authenticated_header = no 99 | 100 | {{ if eq .DBDRIVER "ldap" }} 101 | smtpd_sender_login_maps = ldap:/etc/postfix/ldap/sender-login-maps.cf 102 | {{ else }} 103 | smtpd_sender_login_maps = proxy:{{ .DBDRIVER }}:/etc/postfix/sql/sender-login-maps.cf 104 | {{ end }} 105 | broken_sasl_auth_clients = yes 106 | 107 | ############################## 108 | ## VIRTUALS MAPS PARAMETERS ## 109 | ############################## 110 | 111 | virtual_uid_maps = static:{{ .VMAILUID }} 112 | virtual_gid_maps = static:{{ .VMAILGID }} 113 | virtual_minimum_uid = {{ .VMAILUID }} 114 | virtual_mailbox_base = /var/mail 115 | virtual_transport = lmtp:unix:private/dovecot-lmtp 116 | 117 | {{ if eq .DBDRIVER "ldap" }} 118 | virtual_mailbox_domains = ldap:/etc/postfix/ldap/virtual-mailbox-domains.cf 119 | virtual_mailbox_maps = ldap:/etc/postfix/ldap/virtual-mailbox-maps.cf 120 | virtual_alias_maps = ldap:/etc/postfix/ldap/virtual-alias-maps.cf,{{ if ne .LDAP_FORWARD_ATTRIBUTE "" }} 121 | ldap:/etc/postfix/ldap/virtual-forward-maps.cf,{{ end }}{{ if ne .LDAP_GROUP_ATTRIBUTE "" }} 122 | ldap:/etc/postfix/ldap/virtual-group-maps.cf,{{ end }} 123 | hash:/etc/postfix/virtual 124 | {{ else }} 125 | virtual_mailbox_domains = proxy:{{ .DBDRIVER }}:/etc/postfix/sql/virtual-mailbox-domains.cf 126 | virtual_mailbox_maps = proxy:{{ .DBDRIVER }}:/etc/postfix/sql/virtual-mailbox-maps.cf, 127 | proxy:{{ .DBDRIVER }}:/etc/postfix/sql/virtual-alias-domain-mailbox-maps.cf 128 | virtual_alias_maps = proxy:{{ .DBDRIVER }}:/etc/postfix/sql/virtual-alias-maps.cf, 129 | proxy:{{ .DBDRIVER }}:/etc/postfix/sql/virtual-alias-domain-maps.cf, 130 | proxy:{{ .DBDRIVER }}:/etc/postfix/sql/virtual-alias-domain-catchall-maps.cf, 131 | hash:/etc/postfix/virtual 132 | {{ end }} 133 | 134 | ###################### 135 | ## ERRORS REPORTING ## 136 | ###################### 137 | 138 | # notify_classes = bounce, delay, resource, software 139 | notify_classes = resource, software 140 | 141 | error_notice_recipient = postmaster@{{ .DOMAIN }} 142 | # delay_notice_recipient = postmaster@{{ .DOMAIN }} 143 | # bounce_notice_recipient = postmaster@{{ .DOMAIN }} 144 | # 2bounce_notice_recipient = postmaster@{{ .DOMAIN }} 145 | 146 | ################## 147 | ## RESTRICTIONS ## 148 | ################## 149 | 150 | ## 151 | # Access restrictions for mail relay control that the Postfix SMTP server applies 152 | # in the context of the RCPT TO command, before smtpd_recipient_restrictions 153 | ## 154 | 155 | # * permit_mynetworks : Permit the request when the client IP address matches any trusted network 156 | # * permit_sasl_authenticated : Permit the request when the client is successfully authenticated 157 | # * reject_unauth_destination : No one else, reject all others relaying requests 158 | 159 | smtpd_relay_restrictions= 160 | permit_mynetworks, 161 | permit_sasl_authenticated, 162 | reject_unauth_destination 163 | 164 | ## 165 | # Restrictions that the Postfix SMTP server applies in the context 166 | # of a client MAIL FROM command 167 | ## 168 | 169 | # * reject_non_fqdn_sender : Reject when the MAIL FROM address is not in fully-qualified domain form 170 | # * reject_unknown_sender_domain : Reject when the MAIL FROM domain has no DNS MX, no DNS A record or a malformed MX record 171 | # * reject_sender_login_mismatch: Reject when the client is not (SASL) logged in as that MAIL FROM address owner or when the client is (SASL) logged in, but the client login name doesn't own the MAIL FROM address 172 | # * reject_rhsbl_sender : Reject when the MAIL FROM domain is blacklisted in dbl.spamhaus.org 173 | 174 | smtpd_sender_restrictions= 175 | reject_non_fqdn_sender, 176 | reject_unknown_sender_domain, 177 | reject_sender_login_mismatch, 178 | reject_unlisted_sender, 179 | reject_rhsbl_sender dbl.spamhaus.org, 180 | check_sender_access hash:/etc/postfix/sender_access 181 | 182 | ## 183 | # Restrictions that the Postfix SMTP server applies in the context 184 | # of a client RCPT TO command, after smtpd_relay_restrictions 185 | ## 186 | 187 | # * permit_mynetworks : Permit the request when the client IP address matches any trusted network 188 | # * permit_sasl_authenticated : Permit the request when the client is successfully authenticated 189 | # * reject_unknown_recipient_domain : Reject when the RCPT TO domain has no DNS MX or no DNS A record or a malformed MX record 190 | # * reject_non_fqdn_recipient : Reject when the RCPT TO address is not in fully-qualified domain form 191 | # * reject_unlisted_recipient : Reject when the RCPT TO address is not listed in the list of valid recipients for its domain 192 | # * reject_rbl_client : Reject connections from IP addresses blacklisted in zen.spamhaus.org 193 | 194 | smtpd_recipient_restrictions= 195 | permit_mynetworks, 196 | permit_sasl_authenticated, 197 | reject_unknown_recipient_domain, 198 | reject_non_fqdn_recipient, 199 | reject_unlisted_recipient, 200 | reject_rbl_client zen.spamhaus.org 201 | 202 | ## 203 | # Restrictions that the Postfix SMTP server applies in the context of a client HELO command 204 | ## 205 | 206 | # Fully enforce helo restriction 207 | # without "smtpd_helo_required = yes", a client can simply skip 208 | # smtpd_helo_restrictions by not sending HELO or EHLO 209 | smtpd_helo_required = yes 210 | 211 | # * permit_mynetworks : Permit the request when the client IP address matches any trusted network 212 | # * permit_sasl_authenticated : Permit the request when the client is successfully authenticated 213 | # * reject_invalid_helo_hostname : Reject the request when the HELO or EHLO hostname is malformed 214 | # * reject_non_fqdn_helo_hostname : Reject the request when the HELO or EHLO hostname is not in fully-qualified domain 215 | 216 | smtpd_helo_restrictions = 217 | permit_mynetworks, 218 | permit_sasl_authenticated, 219 | reject_invalid_helo_hostname, 220 | reject_non_fqdn_helo_hostname 221 | 222 | ############ 223 | ## RSPAMD ## 224 | ############ 225 | 226 | milter_protocol = 6 227 | milter_default_action = accept 228 | smtpd_milters = inet:localhost:11332 229 | non_smtpd_milters = inet:localhost:11332 230 | 231 | ############ 232 | ## ZEYPLE ## 233 | ############ 234 | 235 | content_filter = zeyple 236 | 237 | ####################### 238 | ## YOUR CUSTOM RULES ## 239 | ####################### 240 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/master.cf: -------------------------------------------------------------------------------- 1 | # 2 | # Postfix master process configuration file. For details on the format 3 | # of the file, see the master(5) manual page (command: "man 5 master"). 4 | # 5 | # Do not forget to execute "postfix reload" after editing this file. 6 | # 7 | # ========================================================================== 8 | # service type private unpriv chroot wakeup maxproc command + args 9 | # (yes) (yes) (yes) (never) (100) 10 | # ========================================================================== 11 | smtp inet n - - - - smtpd 12 | 13 | submission inet n - - - - smtpd 14 | -o syslog_name=postfix/submission 15 | -o smtpd_tls_security_level=encrypt 16 | -o smtpd_tls_ciphers=high 17 | -o smtpd_client_restrictions=permit_sasl_authenticated,reject 18 | -o cleanup_service_name=authclean 19 | 20 | smtps inet n - - - - smtpd 21 | -o syslog_name=postfix/smtps 22 | -o smtpd_tls_wrappermode=yes 23 | -o smtpd_tls_ciphers=high 24 | -o smtpd_client_restrictions=permit_sasl_authenticated,reject 25 | -o cleanup_service_name=authclean 26 | 27 | authclean unix n - - - 0 cleanup 28 | -o syslog_name=postfix/authclean 29 | -o header_checks=pcre:/etc/postfix/header_checks 30 | 31 | pickup fifo n - - 60 1 pickup 32 | -o content_filter= 33 | -o receive_override_options=no_header_body_checks 34 | 35 | cleanup unix n - - - 0 cleanup 36 | qmgr fifo n - n 300 1 qmgr 37 | tlsmgr unix - - - 1000? 1 tlsmgr 38 | rewrite unix - - - - - trivial-rewrite 39 | bounce unix - - - - 0 bounce 40 | defer unix - - - - 0 bounce 41 | trace unix - - - - 0 bounce 42 | verify unix - - - - 1 verify 43 | flush unix n - - 1000? 0 flush 44 | proxymap unix - - n - - proxymap 45 | proxywrite unix - - n - 1 proxymap 46 | smtp unix - - - - - smtp 47 | relay unix - - - - - smtp 48 | showq unix n - - - - showq 49 | error unix - - - - - error 50 | retry unix - - - - - error 51 | discard unix - - - - - discard 52 | local unix - n n - - local 53 | virtual unix - n n - - virtual 54 | lmtp unix - - - - - lmtp 55 | anvil unix - - - - 1 anvil 56 | scache unix - - - - 1 scache 57 | 58 | zeyple unix - n n - - pipe 59 | user=zeyple argv=/usr/local/bin/zeyple.py ${recipient} 60 | 61 | 127.0.0.1:10026 inet n - - - 10 smtpd 62 | -o content_filter= 63 | -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters 64 | -o smtpd_helo_restrictions= 65 | -o smtpd_client_restrictions= 66 | -o smtpd_sender_restrictions= 67 | -o smtpd_recipient_restrictions=permit_mynetworks,reject 68 | -o mynetworks=127.0.0.0/8,[::1]/128 69 | -o smtpd_authorized_xforward_hosts=127.0.0.0/8,[::1]/128 70 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/sql/sender-login-maps.cf: -------------------------------------------------------------------------------- 1 | hosts = {{ .DBHOST }}:{{ .DBPORT }} 2 | user = {{ .DBUSER }} 3 | password = {{ .DBPASS }} 4 | dbname = {{ .DBNAME }} 5 | 6 | {{ if eq .DBDRIVER "mysql" }} 7 | option_group = client 8 | {{ end }} 9 | 10 | query = SELECT username FROM mailbox WHERE username='%s' AND active = TRUE UNION SELECT goto FROM alias WHERE address='%s' AND active = TRUE 11 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/sql/virtual-alias-domain-catchall-maps.cf: -------------------------------------------------------------------------------- 1 | hosts = {{ .DBHOST }}:{{ .DBPORT }} 2 | user = {{ .DBUSER }} 3 | password = {{ .DBPASS }} 4 | dbname = {{ .DBNAME }} 5 | 6 | {{ if eq .DBDRIVER "mysql" }} 7 | option_group = client 8 | {{ end }} 9 | 10 | query = SELECT goto FROM alias, alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = TRUE AND alias_domain.active = TRUE 11 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/sql/virtual-alias-domain-mailbox-maps.cf: -------------------------------------------------------------------------------- 1 | hosts = {{ .DBHOST }}:{{ .DBPORT }} 2 | user = {{ .DBUSER }} 3 | password = {{ .DBPASS }} 4 | dbname = {{ .DBNAME }} 5 | 6 | {{ if eq .DBDRIVER "mysql" }} 7 | option_group = client 8 | {{ end }} 9 | 10 | query = SELECT maildir FROM mailbox, alias_domain WHERE alias_domain.alias_domain = '%d' AND mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = TRUE AND alias_domain.active = TRUE 11 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/sql/virtual-alias-domain-maps.cf: -------------------------------------------------------------------------------- 1 | hosts = {{ .DBHOST }}:{{ .DBPORT }} 2 | user = {{ .DBUSER }} 3 | password = {{ .DBPASS }} 4 | dbname = {{ .DBNAME }} 5 | 6 | {{ if eq .DBDRIVER "mysql" }} 7 | option_group = client 8 | {{ end }} 9 | 10 | query = SELECT goto FROM alias, alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = TRUE AND alias_domain.active = TRUE 11 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/sql/virtual-alias-maps.cf: -------------------------------------------------------------------------------- 1 | hosts = {{ .DBHOST }}:{{ .DBPORT }} 2 | user = {{ .DBUSER }} 3 | password = {{ .DBPASS }} 4 | dbname = {{ .DBNAME }} 5 | 6 | {{ if eq .DBDRIVER "mysql" }} 7 | option_group = client 8 | {{ end }} 9 | 10 | query = SELECT goto FROM alias WHERE address='%s' AND active = TRUE 11 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/sql/virtual-mailbox-domains.cf: -------------------------------------------------------------------------------- 1 | hosts = {{ .DBHOST }}:{{ .DBPORT }} 2 | user = {{ .DBUSER }} 3 | password = {{ .DBPASS }} 4 | dbname = {{ .DBNAME }} 5 | 6 | {{ if eq .DBDRIVER "mysql" }} 7 | option_group = client 8 | {{ end }} 9 | 10 | query = SELECT domain FROM domain WHERE domain='%s' and backupmx = FALSE and active = TRUE 11 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/sql/virtual-mailbox-maps.cf: -------------------------------------------------------------------------------- 1 | hosts = {{ .DBHOST }}:{{ .DBPORT }} 2 | user = {{ .DBUSER }} 3 | password = {{ .DBPASS }} 4 | dbname = {{ .DBNAME }} 5 | 6 | {{ if eq .DBDRIVER "mysql" }} 7 | option_group = client 8 | {{ end }} 9 | 10 | query = SELECT maildir FROM mailbox WHERE username='%s' AND active = TRUE 11 | -------------------------------------------------------------------------------- /rootfs/etc/postfix/virtual: -------------------------------------------------------------------------------- 1 | root postmaster@{{ .DOMAIN }} 2 | clamav postmaster@{{ .DOMAIN }} 3 | postmaster postmaster@{{ .DOMAIN }} 4 | -------------------------------------------------------------------------------- /rootfs/etc/postfixadmin/fetchmail.conf: -------------------------------------------------------------------------------- 1 | {{ if eq .DBDRIVER "mysql" }} 2 | $db_type = 'mysql'; 3 | {{ else if eq .DBDRIVER "pgsql" }} 4 | $db_type = 'Pg'; 5 | {{ end }} 6 | $db_host="{{ .DBHOST }}"; 7 | $db_port="{{ .DBPORT }}"; 8 | $db_name="{{ .DBNAME }}"; 9 | $db_username="{{ .DBUSER }}"; 10 | $db_password="{{ .DBPASS }}"; 11 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/antivirus.conf: -------------------------------------------------------------------------------- 1 | clamav { 2 | enabled = true; 3 | action = "reject"; 4 | scan_mime_parts = false; 5 | symbol = "CLAM_VIRUS"; 6 | type = "clamav"; 7 | log_clean = true; 8 | servers = "localhost:3310"; 9 | retransmits = 4; 10 | } 11 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/arc.conf: -------------------------------------------------------------------------------- 1 | allow_envfrom_empty = false; 2 | allow_hdrfrom_mismatch = false; 3 | allow_hdrfrom_multiple = true; 4 | allow_username_mismatch = true; 5 | auth_only = true; 6 | path = "/var/mail/dkim/$domain/private.key"; 7 | selector = "mail"; 8 | sign_local = true; 9 | symbol = "ARC_SIGNED"; 10 | try_fallback = true; 11 | use_domain = "header"; 12 | use_esld = false; 13 | use_redis = false; 14 | key_prefix = "ARC_KEYS"; 15 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/dkim_signing.conf: -------------------------------------------------------------------------------- 1 | allow_envfrom_empty = false; 2 | allow_hdrfrom_mismatch = false; 3 | allow_hdrfrom_multiple = true; 4 | allow_username_mismatch = true; 5 | auth_only = true; 6 | path = "/var/mail/dkim/$domain/private.key"; 7 | selector = "mail"; 8 | sign_local = true; 9 | symbol = "DKIM_SIGNED"; 10 | try_fallback = true; 11 | use_domain = "header"; 12 | use_esld = false; 13 | use_redis = false; 14 | key_prefix = "DKIM_KEYS"; 15 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/groups.conf: -------------------------------------------------------------------------------- 1 | group "MX" { 2 | 3 | symbol "MX_INVALID" { 4 | score = 1.0; 5 | description = "No connectable MX"; 6 | one_shot = "true"; 7 | } 8 | 9 | symbol "MX_MISSING" { 10 | score = 2.0; 11 | description = "No MX record"; 12 | one_shot = "true"; 13 | } 14 | 15 | symbol "MX_GOOD" { 16 | score = -0.5; 17 | description = "MX was ok"; 18 | one_shot = "true"; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/logging.inc: -------------------------------------------------------------------------------- 1 | level = "warning"; 2 | type = "syslog"; 3 | systemd = false; 4 | facility = "mail" 5 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/milter_headers.conf: -------------------------------------------------------------------------------- 1 | use = ["spam-header", "x-spam-level", "x-spam-status", "x-virus", "authentication-results"]; 2 | 3 | skip_local = false; 4 | skip_authenticated = true; 5 | extended_spam_headers = true; 6 | 7 | routines { 8 | spam-header { 9 | header = "X-Spam-Flag"; 10 | remove = 1; 11 | value = "YES"; 12 | } 13 | x-spam-level { 14 | header = "X-Spam-Level"; 15 | remove = 1; 16 | char = "*"; 17 | } 18 | x-spam-status { 19 | header = "X-Spam-Status"; 20 | remove = 1; 21 | } 22 | x-virus { 23 | header = "X-Virus"; 24 | remove = 1; 25 | symbols = ["CLAM_VIRUS"]; 26 | } 27 | authentication-results { 28 | header = "Authentication-Results"; 29 | remove = 1; 30 | add_smtp_user = false; 31 | spf_symbols { 32 | pass = "R_SPF_ALLOW"; 33 | fail = "R_SPF_FAIL"; 34 | softfail = "R_SPF_SOFTFAIL"; 35 | neutral = "R_SPF_NEUTRAL"; 36 | temperror = "R_SPF_DNSFAIL"; 37 | none = "R_SPF_NA"; 38 | permerror = "R_SPF_PERMFAIL"; 39 | } 40 | dkim_symbols { 41 | pass = "R_DKIM_ALLOW"; 42 | fail = "R_DKIM_REJECT"; 43 | temperror = "R_DKIM_TEMPFAIL"; 44 | none = "R_DKIM_NA"; 45 | permerror = "R_DKIM_PERMFAIL"; 46 | } 47 | dmarc_symbols { 48 | pass = "DMARC_POLICY_ALLOW"; 49 | permerror = "DMARC_BAD_POLICY"; 50 | temperror = "DMARC_DNSFAIL"; 51 | none = "DMARC_NA"; 52 | reject = "DMARC_POLICY_REJECT"; 53 | softfail = "DMARC_POLICY_SOFTFAIL"; 54 | quarantine = "DMARC_POLICY_QUARANTINE"; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/mime_types.conf: -------------------------------------------------------------------------------- 1 | # Extensions that are treated as 'bad' 2 | # Number is score multiply factor 3 | bad_extensions = { 4 | scr = 4, 5 | lnk = 4, 6 | exe = 1, 7 | jar = 2, 8 | com = 4, 9 | bat = 4, 10 | ace = 4, 11 | arj = 4, 12 | cab = 3, 13 | }; 14 | 15 | # Extensions that are particularly penalized for archives 16 | bad_archive_extensions = { 17 | pptx = 0.5, 18 | docx = 0.5, 19 | xlsx = 0.5, 20 | pdf = 1.0, 21 | jar = 3, 22 | js = 0.5, 23 | vbs = 7, 24 | }; 25 | 26 | # Used to detect another archive in archive 27 | archive_extensions = { 28 | zip = 1, 29 | arj = 1, 30 | rar = 1, 31 | ace = 1, 32 | 7z = 1, 33 | cab = 1, 34 | }; 35 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/mx_check.conf: -------------------------------------------------------------------------------- 1 | enabled = true; 2 | timeout = 1.0; 3 | symbol_bad_mx = "MX_INVALID"; 4 | symbol_no_mx = "MX_MISSING"; 5 | symbol_good_mx = "MX_GOOD"; 6 | expire = 86400; 7 | expire_novalid = 7200; 8 | greylist_invalid = false; 9 | key_prefix = "rmx"; 10 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/neural.conf: -------------------------------------------------------------------------------- 1 | train { 2 | # Number ham/spam samples needed to start train 3 | max_train = 30; 4 | # Number of learn iterations while ANN data is valid 5 | max_usages = 150; 6 | # Score to learn spam 7 | spam_score = 8; 8 | # Score to learn ham 9 | ham_score = -2; 10 | # Rate of learning 11 | learning_rate = 0.01; 12 | # Maximum iterations of learning (better preciseness but also lower speed of learning) 13 | max_iterations = 25; 14 | } 15 | 16 | symbol_spam = "NEURAL_SPAM"; 17 | symbol_ham = "NEURAL_HAM"; 18 | ann_expire = 30d; 19 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/neural_group.conf: -------------------------------------------------------------------------------- 1 | symbols = { 2 | 3 | "NEURAL_SPAM" { 4 | weight = 3.0; 5 | description = "Neural network spam"; 6 | } 7 | 8 | "NEURAL_HAM" { 9 | weight = -3.0; 10 | description = "Neural network ham"; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/options.inc: -------------------------------------------------------------------------------- 1 | map_watch_interval = 1min; 2 | dns { 3 | enable_dnssec = true; 4 | timeout = 4s; 5 | retransmits = 5; 6 | # In this case, 127.0.0.11 (docker embedded DNS server) 7 | # will be used as a backup when local resolver returns 8 | # an error (i.e SERVFAIL) or can't be reached 9 | nameserver = "master-slave:127.0.0.1:53:10,127.0.0.11:53:1"; 10 | } 11 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/ratelimit.conf: -------------------------------------------------------------------------------- 1 | rates { 2 | # Limit for all mail per recipient (rate 4 per minute) 3 | to = "4 / 1m"; 4 | # Limit for all mail per one source ip (rate 6 per minute) 5 | to_ip = "6 / 1m"; 6 | # Limit for all mail per one source ip and from address (rate 4 per minute) 7 | to_ip_from = "4 / 1m"; 8 | # Limit for all bounce mail (rate 2 per hour) 9 | bounce_to = "2 / 1h"; 10 | # Limit for bounce mail per one source ip (rate 1 per hour) 11 | bounce_to_ip = "1 / 1h"; 12 | # Limit for all mail per authenticated user (rate 4 per minute) 13 | user = "4 / 1m"; 14 | } 15 | 16 | whitelisted_rcpts = "postmaster,mailer-daemon"; 17 | max_rcpt = 5; 18 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/redis.conf: -------------------------------------------------------------------------------- 1 | servers = "{{ .REDIS_HOST }}:{{ .REDIS_PORT }}"; 2 | {{ if ne .REDIS_PASS "" }} 3 | password = "{{ .REDIS_PASS }}" 4 | {{ end }} 5 | db = {{ .REDIS_NUMB }} 6 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/settings.conf: -------------------------------------------------------------------------------- 1 | whitelist { 2 | priority = low; 3 | rcpt = "postmaster@{{ .DOMAIN }}"; 4 | want_spam = yes; 5 | } 6 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/statistic.conf: -------------------------------------------------------------------------------- 1 | classifier "bayes" { 2 | 3 | tokenizer { 4 | name = "osb"; 5 | } 6 | 7 | # Backend configuration 8 | backend = "redis"; 9 | servers = "{{ .REDIS_HOST }}:{{ .REDIS_PORT }}"; 10 | {{ if ne .REDIS_PASS "" }} 11 | password = "{{ .REDIS_PASS }}" 12 | {{ end }} 13 | database = {{ .REDIS_NUMB }} 14 | 15 | # Minimum learn count for both spam and ham classes 16 | # to perform classification 17 | min_learns = 30; 18 | 19 | # Autolearning is performing as spam if a message has reject 20 | # action and as ham if a message has negative score 21 | autolearn = true; 22 | 23 | # Use new schema (1.7+) 24 | new_schema = true; 25 | 26 | # Expire bayes tokens 27 | expire = 100d; 28 | 29 | # Enable per user statistics 30 | per_user = <= 0.95 65 | else 66 | cl = 'ham' 67 | in_class = prob <= 0.05 68 | end 69 | 70 | if in_class then 71 | return false,string.format('already in class %s; probability %.2f%%', 72 | cl, math.abs((prob - 0.5) * 200.0)) 73 | end 74 | end 75 | 76 | return true 77 | end 78 | EOD 79 | } 80 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/worker-controller.inc: -------------------------------------------------------------------------------- 1 | bind_socket = "*:11334"; 2 | password = ""; 3 | enable_password = ""; 4 | secure_ip = "127.0.0.1"; 5 | secure_ip = "::1"; 6 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/worker-fuzzy.inc: -------------------------------------------------------------------------------- 1 | enabled = false; 2 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/worker-normal.inc: -------------------------------------------------------------------------------- 1 | enabled = false; 2 | -------------------------------------------------------------------------------- /rootfs/etc/rspamd/local.d/worker-proxy.inc: -------------------------------------------------------------------------------- 1 | bind_socket = "*:11332"; 2 | 3 | upstream "local" { 4 | self_scan = yes; 5 | } 6 | -------------------------------------------------------------------------------- /rootfs/etc/rsyslog/rsyslog.conf: -------------------------------------------------------------------------------- 1 | $ModLoad imuxsock 2 | $WorkDirectory /var/spool/rsyslog 3 | $IncludeConfig /etc/rsyslog.d/*.conf 4 | 5 | # Specific rspamd and clamav warnings that can be ignored, some are intended 6 | # https://github.com/vstakhov/rspamd/issues/1474 7 | # https://github.com/vstakhov/rspamd/issues/1693 8 | :msg,contains,"map file is unavailable for reading" ~ 9 | :msg,contains,"cannot load controller stats from /var/mail/rspamd/stats.ucl" ~ 10 | :msg,contains,"database is locked" ~ 11 | :msg,contains,"http error occurred: IO read error: unexpected EOF" ~ 12 | :msg,contains,"http error occurred: Not found" ~ 13 | :msg,contains,"Clamd was NOT notified" ~ 14 | :msg,contains,"symlink leaves directory" ~ 15 | 16 | mail.* /dev/stdout 17 | & /var/log/mail.log 18 | mail.warn /var/log/mail.warn 19 | mail.err /var/log/mail.err 20 | *.* /dev/null 21 | -------------------------------------------------------------------------------- /rootfs/etc/unbound/unbound.conf: -------------------------------------------------------------------------------- 1 | server: 2 | verbosity: 0 3 | interface: 127.0.0.1 4 | logfile: /dev/null 5 | hide-identity: yes 6 | hide-version: yes 7 | do-ip4: yes 8 | do-ip6: no 9 | do-udp: yes 10 | do-tcp: yes 11 | do-daemonize: no 12 | username: unbound 13 | directory: "/etc/unbound" 14 | root-hints: "/etc/unbound/root.hints" 15 | auto-trust-anchor-file: "root.key" 16 | access-control: 127.0.0.0/8 allow 17 | max-udp-size: 4096 18 | msg-buffer-size: 65552 19 | 20 | remote-control: 21 | control-enable: yes 22 | control-interface: 127.0.0.1 23 | -------------------------------------------------------------------------------- /rootfs/etc/zeyple/zeyple.conf: -------------------------------------------------------------------------------- 1 | [zeyple] 2 | log_file = /dev/null 3 | 4 | [gpg] 5 | home = /var/mail/zeyple/keys 6 | 7 | [relay] 8 | host = localhost 9 | port = 10026 10 | -------------------------------------------------------------------------------- /rootfs/services/.s6-svscan/finish: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Make sure all services have actually exited *completely* 4 | for svc in /services/[a-z]*; do [ -d "$svc" ] && redirfd -w 2 /dev/null s6-svc -d -wD "$svc"; done 5 | 6 | # Remove leftover pid files from a stop/start 7 | rm -rf /var/run/*.pid /var/run/*/*.pid 8 | 9 | # Avoid "Compromised token secret file" error after container restart 10 | rm -f /var/run/dovecot/auth-token-secret.dat 11 | 12 | exit 0 13 | -------------------------------------------------------------------------------- /rootfs/services/_parent/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Launch all services in order 4 | # ----------------------------------------------------------- 5 | # 1. rsyslogd (logger) 6 | # 2. unbound (dns resolver) 7 | # 3. postfix (smtp server) 8 | # 4. dovecot (imap/pop server) 9 | # 5. rspamd (anti-spam) 10 | # 6. cron (job scheduler) 11 | # 7. freshclam (clamav database updater) 12 | # 8. clamd (antivirus, is launched after database update) 13 | # 9. watcher (watches for cert file changes) 14 | 15 | s6-svc -u /services/rsyslogd && s6-svwait -u /services/rsyslogd 16 | s6-svc -u /services/unbound && [ "$DISABLE_DNS_RESOLVER" = true ] || s6-svwait -u /services/unbound 17 | s6-svc -u /services/postfix && s6-svwait -u /services/postfix 18 | s6-svc -u /services/dovecot 19 | s6-svc -u /services/rspamd 20 | s6-svc -u /services/cron 21 | 22 | if [ "$DISABLE_CLAMAV" = false ]; then 23 | s6-svc -u /services/freshclam 24 | if [ -f "/var/mail/clamav-unofficial-sigs/user.conf" ]; then 25 | logger -p mail.info "s6-supervise : clamav unofficial signature update running" 26 | clamav-unofficial-sigs.sh &>/dev/null 27 | logger -p mail.info "s6-supervise : clamav unofficial signature update done" 28 | fi 29 | s6-svwait -u /services/clamd 30 | fi 31 | 32 | s6-svc -u /services/cert_watcher 33 | 34 | sleep 2 35 | logger -p mail.info "s6-supervise : (_parent) all my wonderful children have started, i can die in peace :)" 36 | exit 0 37 | -------------------------------------------------------------------------------- /rootfs/services/cert_watcher/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/rootfs/services/cert_watcher/down -------------------------------------------------------------------------------- /rootfs/services/cert_watcher/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$DISABLE_SSL_WATCH" = true ]; then 4 | exit 0 5 | fi 6 | 7 | logger -p mail.info "s6-supervise : spawning SSL watcher process" 8 | exec certs_helper.sh watch &> /dev/stdout 9 | -------------------------------------------------------------------------------- /rootfs/services/clamd/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/rootfs/services/clamd/down -------------------------------------------------------------------------------- /rootfs/services/clamd/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | logger -p mail.info "s6-supervise : virus database downloaded, spawning clamd process" 3 | 4 | echo $$>/var/run/clamav/clamd.pid 5 | exec clamd &>/dev/null 6 | -------------------------------------------------------------------------------- /rootfs/services/cron/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/rootfs/services/cron/down -------------------------------------------------------------------------------- /rootfs/services/cron/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | logger -p mail.info "s6-supervise : spawning cron process" 3 | exec cron -f 4 | -------------------------------------------------------------------------------- /rootfs/services/dovecot/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/rootfs/services/dovecot/down -------------------------------------------------------------------------------- /rootfs/services/dovecot/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | logger -p mail.info "s6-supervise : spawning dovecot process" 3 | exec dovecot -F &>/dev/null 4 | -------------------------------------------------------------------------------- /rootfs/services/freshclam/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/rootfs/services/freshclam/down -------------------------------------------------------------------------------- /rootfs/services/freshclam/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | logger -p mail.info "s6-supervise : spawning freshclam process" 4 | 5 | # Download virus databases if they don't exist locally 6 | if [ ! -f /var/mail/clamav/main.cvd ]; then 7 | freshclam --quiet 8 | fi 9 | 10 | # Start clamd run script 11 | s6-svc -u /services/clamd 12 | 13 | echo $$>/var/run/clamav/freshclam.pid 14 | exec freshclam -d &>/dev/null 15 | -------------------------------------------------------------------------------- /rootfs/services/postfix/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/rootfs/services/postfix/down -------------------------------------------------------------------------------- /rootfs/services/postfix/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | logger -p mail.info "s6-supervise : spawning postfix process" 3 | exec postfix -c /etc/postfix start-fg &>/dev/null 4 | -------------------------------------------------------------------------------- /rootfs/services/rspamd/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/rootfs/services/rspamd/down -------------------------------------------------------------------------------- /rootfs/services/rspamd/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | logger -p mail.info "s6-supervise : spawning rspamd process" 3 | exec rspamd -f -u _rspamd -g _rspamd 4 | -------------------------------------------------------------------------------- /rootfs/services/rsyslogd/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/rootfs/services/rsyslogd/down -------------------------------------------------------------------------------- /rootfs/services/rsyslogd/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec rsyslogd -n -f /etc/rsyslog/rsyslog.conf 3 | -------------------------------------------------------------------------------- /rootfs/services/unbound/down: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/rootfs/services/unbound/down -------------------------------------------------------------------------------- /rootfs/services/unbound/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$DISABLE_DNS_RESOLVER" = true ]; then 4 | cp -f /etc/resolv.conf /var/mail/postfix/spool/etc/resolv.conf 5 | exit 0 6 | fi 7 | 8 | # Use the local DNS server 9 | echo "nameserver 127.0.0.1" | tee /etc/resolv.conf \ 10 | /var/mail/postfix/spool/etc/resolv.conf \ 11 | >/dev/null 12 | 13 | logger -p mail.info "s6-supervise : spawning unbound process" 14 | exec unbound &> /dev/stdout 15 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/certs_helper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ACME_PATH=/etc/letsencrypt/acme 4 | ACME_FILE="$ACME_PATH"/acme.json 5 | SSL_MOUNT_PATH=/var/mail/ssl 6 | ACME_DUMP="$SSL_MOUNT_PATH"/acme_dump.log 7 | SELFSIGNED_PATH="$SSL_MOUNT_PATH"/selfsigned 8 | CERT_TEMP_PATH=/tmp/ssl 9 | LETS_ENCRYPT_LIVE_PATH=/etc/letsencrypt/live/"$FQDN" 10 | LIVE_CERT_PATH=/ssl 11 | RENEWED_CERTIFICATE=false 12 | 13 | _normalize_certs() { 14 | SSL_DIR="$1" 15 | FULLCHAIN="$SSL_DIR"/fullchain.pem 16 | CAFILE="$SSL_DIR"/chain.pem 17 | CERTFILE="$SSL_DIR"/cert.pem 18 | KEYFILE="$SSL_DIR"/privkey.pem 19 | 20 | # When using https://github.com/jwilder/nginx-proxy there is only key.pem 21 | if [ -e "$SSL_DIR"/key.pem ]; then 22 | mv -f "$SSL_DIR"/key.pem "$KEYFILE" 23 | fi 24 | 25 | if [ ! -e "$KEYFILE" ]; then 26 | echo "[ERROR] No keyfile found in $SSL_DIR !" 27 | exit 1 28 | fi 29 | 30 | if [ "$RENEWED_CERTIFICATE" = true ] || [ ! -e "$CAFILE" ] || [ ! -e "$CERTFILE" ]; then 31 | if [ -e "$FULLCHAIN" ]; then 32 | # Extract cert.pem and chain.pem from fullchain.pem 33 | # Used for containous/traefik and jwilder/nginx-proxy 34 | awk -v path="$SSL_DIR" 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > path"/cert" c ".pem"}' < "$FULLCHAIN" 35 | mv "$SSL_DIR"/cert1.pem "$CERTFILE" 36 | mv "$SSL_DIR"/cert2.pem "$CAFILE" 37 | fi 38 | fi 39 | 40 | if [ ! -e "$FULLCHAIN" ]; then 41 | cp "$CERTFILE" "$FULLCHAIN" 42 | fi 43 | } 44 | 45 | if [ "$1" = "watch" ]; then 46 | echo "[INFO] Checking for watchable SSL certificates" 47 | if [ -f "$ACME_FILE" ]; then 48 | exec watcher.py "$ACME_PATH" 49 | elif [ -d "$LETS_ENCRYPT_LIVE_PATH" ]; then 50 | exec watcher.py "$LETS_ENCRYPT_LIVE_PATH" 51 | else 52 | echo "[INFO] No watchable SSL certificate mounts - disabling SSL watcher" 53 | fi 54 | 55 | elif [ "$1" = "update_certs" ]; then 56 | mkdir -p "$SSL_MOUNT_PATH" 57 | mkdir -p "$CERT_TEMP_PATH" 58 | mkdir -p "$LIVE_CERT_PATH" 59 | rm -rf "$CERT_TEMP_PATH/*" 60 | 61 | NORMALIZED_CERT_PATH="$CERT_TEMP_PATH"/normalized 62 | mkdir -p "$NORMALIZED_CERT_PATH" 63 | 64 | if [ -f "$ACME_FILE" ]; then 65 | echo "[INFO] Search for SSL certificates generated by Traefik" 66 | 67 | if [ ! "$2" = "-n" ]; then 68 | while [ ! -s "$ACME_FILE" ]; do 69 | sleep 5 70 | echo "[INFO] ..." 71 | done 72 | 73 | # Wait for acme.json full filling 74 | sleep 10 75 | fi 76 | 77 | if jq -e -r '.PrivateKey' "$ACME_FILE" >/dev/null ; then 78 | echo "[INFO] acme.json found with ACME v1 format, dumping into pem files" | tee -a "$ACME_DUMP" 79 | dumpcerts.acme.v1.sh "$ACME_FILE" "$CERT_TEMP_PATH" >> "$ACME_DUMP" 2>&1 80 | elif jq -e -r '.Account.PrivateKey' "$ACME_FILE" >/dev/null ; then 81 | echo "[INFO] acme.json found with ACME v2 format, dumping into pem files" | tee -a "$ACME_DUMP" 82 | dumpcerts.acme.v2.sh "$ACME_FILE" "$CERT_TEMP_PATH" >> "$ACME_DUMP" 2>&1 83 | if [ -e "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" ] && [ -e "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" ]; then 84 | echo "[INFO] Let's encrypt wildcard certificate found" | tee -a "$ACME_DUMP" 85 | mv -f "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" "$CERT_TEMP_PATH"/certs/"$FQDN".crt 86 | mv -f "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" "$CERT_TEMP_PATH"/private/"$FQDN".key 87 | fi 88 | elif jq -e -r '.[].Account.PrivateKey' "$ACME_FILE" >/dev/null ; then 89 | echo "[INFO] acme.json found with Traefik v2 format, dumping into pem files" | tee -a "$ACME_DUMP" 90 | dumpcerts.traefik.v2.sh "$ACME_FILE" "$CERT_TEMP_PATH" >> "$ACME_DUMP" 2>&1 91 | if [ -e "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" ] && [ -e "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" ]; then 92 | echo "[INFO] Let's encrypt wildcard certificate found" | tee -a "$ACME_DUMP" 93 | mv -f "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" "$CERT_TEMP_PATH"/certs/"$FQDN".crt 94 | mv -f "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" "$CERT_TEMP_PATH"/private/"$FQDN".key 95 | fi 96 | else 97 | echo "[ERROR] acme.json found but with an unknown format" >> "$ACME_DUMP" 98 | fi 99 | 100 | if [ -e "$CERT_TEMP_PATH"/certs/"$FQDN".crt ] && [ -e "$CERT_TEMP_PATH"/private/"$FQDN".key ]; then 101 | DOMAIN_NAME="$FQDN" 102 | elif [ -e "$CERT_TEMP_PATH"/certs/"$DOMAIN".crt ] && [ -e "$CERT_TEMP_PATH"/private/"$DOMAIN".key ]; then 103 | DOMAIN_NAME="$DOMAIN" 104 | else 105 | echo "[ERROR] The certificate for ${FQDN} or the private key was not found !" 106 | echo "[INFO] Don't forget to add a new traefik frontend rule to generate a certificate for ${FQDN} subdomain" 107 | echo "[INFO] Look /mnt/docker/traefik/acme/dump.log and 'docker logs traefik' for more information" 108 | exit 1 109 | fi 110 | 111 | mv -f "$CERT_TEMP_PATH"/certs/"$DOMAIN_NAME".crt "$NORMALIZED_CERT_PATH"/fullchain.pem 112 | mv -f "$CERT_TEMP_PATH"/private/"$DOMAIN_NAME".key "$NORMALIZED_CERT_PATH"/privkey.pem 113 | rm -rf "$ACME_DUMP" 114 | RENEWED_CERTIFICATE=true 115 | else 116 | echo "[INFO] Traefik SSL certificates not used" 117 | 118 | if [ -d "$LETS_ENCRYPT_LIVE_PATH" ]; then 119 | echo "[INFO] Let's encrypt live directory found" 120 | echo "[INFO] Using $LETS_ENCRYPT_LIVE_PATH folder" 121 | 122 | cp -RLT "$LETS_ENCRYPT_LIVE_PATH/." "$NORMALIZED_CERT_PATH" 123 | 124 | else 125 | echo "[INFO] No Let's encrypt live directory found" 126 | echo "[INFO] Using "$SELFSIGNED_PATH"/ folder" 127 | 128 | CERTFILE="$SELFSIGNED_PATH"/cert.pem 129 | KEYFILE="$SELFSIGNED_PATH"/privkey.pem 130 | 131 | if [ ! -e "$CERTFILE" ] || [ ! -e "$KEYFILE" ]; then 132 | echo "[INFO] No SSL certificates found, generating a new selfsigned certificate" 133 | mkdir -p /var/mail/ssl/selfsigned/ 134 | openssl req -new -newkey rsa:4096 -days 3658 -sha256 -nodes -x509 \ 135 | -subj "/C=FR/ST=France/L=Paris/O=Mailserver certificate/OU=Mail/CN=*.${DOMAIN}/emailAddress=postmaster@${DOMAIN}" \ 136 | -keyout "$KEYFILE" \ 137 | -out "$CERTFILE" 138 | fi 139 | 140 | cp -RT "$SELFSIGNED_PATH/." "$NORMALIZED_CERT_PATH" 141 | fi 142 | fi 143 | 144 | _normalize_certs "$NORMALIZED_CERT_PATH" 145 | [ $? -ne 0 ] && exit 1 146 | 147 | # Compare Old and New key 148 | if cmp --silent "$NORMALIZED_CERT_PATH"/privkey.pem "$LIVE_CERT_PATH"/privkey.pem; then 149 | echo "[INFO] Live Certificates match" 150 | rm -rf "$CERT_TEMP_PATH" 151 | exit 1 152 | fi 153 | 154 | rm -rf "$LIVE_CERT_PATH/*" 155 | cp -RT "$NORMALIZED_CERT_PATH/." "$LIVE_CERT_PATH" 156 | 157 | elif [ "$1" = "reload" ]; then 158 | echo "[INFO] Updating SSL certificates and reloading" 159 | if "$0" update_certs -n; then 160 | postfix reload 161 | dovecot reload 162 | echo "[INFO] Reloaded Postfix + Dovecot" 163 | fi 164 | 165 | else 166 | echo "[ERROR] Unrecognized command '$1'" 167 | exit 1 168 | fi 169 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/dumpcerts.acme.v1.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) 2017 Brian 'redbeard' Harrington 3 | # 4 | # dumpcerts.sh - A simple utility to explode a Traefik acme.json file into a 5 | # directory of certificates and a private key 6 | # 7 | # Usage - dumpcerts.sh /etc/traefik/acme.json /etc/ssl/ 8 | # 9 | # Dependencies - 10 | # util-linux 11 | # openssl 12 | # jq 13 | # The MIT License (MIT) 14 | # 15 | # Permission is hereby granted, free of charge, to any person obtaining a copy 16 | # of this software and associated documentation files (the "Software"), to deal 17 | # in the Software without restriction, including without limitation the rights 18 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | # copies of the Software, and to permit persons to whom the Software is 20 | # furnished to do so, subject to the following conditions: 21 | # 22 | # The above copyright notice and this permission notice shall be included in 23 | # all copies or substantial portions of the Software. 24 | # 25 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | # THE SOFTWARE. 32 | 33 | # Exit codes: 34 | # 1 - A component is missing or could not be read 35 | # 2 - There was a problem reading acme.json 36 | # 4 - The destination certificate directory does not exist 37 | # 8 - Missing private key 38 | 39 | set -o errexit 40 | set -o pipefail 41 | set -o nounset 42 | 43 | USAGE="$(basename "$0") " 44 | 45 | # Platform variations 46 | case "$(uname)" in 47 | 'Linux') 48 | # On Linux, -d should always work. --decode does not work with Alpine's busybox-binary 49 | CMD_DECODE_BASE64="base64 -d" 50 | ;; 51 | *) 52 | # Max OS-X supports --decode and -D, but --decode may be supported by other platforms as well. 53 | CMD_DECODE_BASE64="base64 --decode" 54 | ;; 55 | esac 56 | 57 | # Allow us to exit on a missing jq binary 58 | exit_jq() { 59 | echo " 60 | You must have the binary 'jq' to use this. 61 | jq is available at: https://stedolan.github.io/jq/download/ 62 | 63 | ${USAGE}" >&2 64 | exit 1 65 | } 66 | 67 | bad_acme() { 68 | echo " 69 | There was a problem parsing your acme.json file. 70 | 71 | ${USAGE}" >&2 72 | exit 2 73 | } 74 | 75 | if [ $# -ne 2 ]; then 76 | echo " 77 | Insufficient number of parameters. 78 | 79 | ${USAGE}" >&2 80 | exit 1 81 | fi 82 | 83 | readonly acmefile="${1}" 84 | readonly certdir="${2%/}" 85 | 86 | if [ ! -r "${acmefile}" ]; then 87 | echo " 88 | There was a problem reading from '${acmefile}' 89 | We need to read this file to explode the JSON bundle... exiting. 90 | 91 | ${USAGE}" >&2 92 | exit 2 93 | fi 94 | 95 | 96 | if [ ! -d "${certdir}" ]; then 97 | echo " 98 | Path ${certdir} does not seem to be a directory 99 | We need a directory in which to explode the JSON bundle... exiting. 100 | 101 | ${USAGE}" >&2 102 | exit 4 103 | fi 104 | 105 | jq=$(command -v jq) || exit_jq 106 | 107 | priv=$(${jq} -e -r '.PrivateKey' "${acmefile}") || bad_acme 108 | 109 | if [ ! -n "${priv}" ]; then 110 | echo " 111 | There didn't seem to be a private key in ${acmefile}. 112 | Please ensure that there is a key in this file and try again." >&2 113 | exit 8 114 | fi 115 | 116 | # If they do not exist, create the needed subdirectories for our assets 117 | # and place each in a variable for later use, normalizing the path 118 | mkdir -p "${certdir}"/{certs,private} 119 | 120 | pdir="${certdir}/private/" 121 | cdir="${certdir}/certs/" 122 | 123 | # Save the existing umask, change the default mode to 600, then 124 | # after writing the private key switch it back to the default 125 | oldumask=$(umask) 126 | umask 177 127 | trap 'umask ${oldumask}' EXIT 128 | 129 | # traefik stores the private key in stripped base64 format but the certificates 130 | # bundled as a base64 object without stripping headers. This normalizes the 131 | # headers and formatting. 132 | # 133 | # In testing this out it was a balance between the following mechanisms: 134 | # gawk: 135 | # echo ${priv} | awk 'BEGIN {print "-----BEGIN RSA PRIVATE KEY-----"} 136 | # {gsub(/.{64}/,"&\n")}1 137 | # END {print "-----END RSA PRIVATE KEY-----"}' > "${pdir}/letsencrypt.key" 138 | # 139 | # openssl: 140 | # echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \ 141 | # | openssl rsa -inform pem -out "${pdir}/letsencrypt.key" 142 | # 143 | # and sed: 144 | # echo "-----BEGIN RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key" 145 | # echo ${priv} | sed -E 's/(.{64})/\1\n/g' >> "${pdir}/letsencrypt.key" 146 | # sed -i '$ d' "${pdir}/letsencrypt.key" 147 | # echo "-----END RSA PRIVATE KEY-----" >> "${pdir}/letsencrypt.key" 148 | # openssl rsa -noout -in "${pdir}/letsencrypt.key" -check # To check if the key is valid 149 | 150 | # In the end, openssl was chosen because most users will need this script 151 | # *because* of openssl combined with the fact that it will refuse to write the 152 | # key if it does not parse out correctly. The other mechanisms were left as 153 | # comments so that the user can choose the mechanism most appropriate to them. 154 | echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \ 155 | | openssl rsa -inform pem -out "${pdir}/letsencrypt.key" >/dev/null 156 | 157 | # Process the certificates for each of the domains in acme.json 158 | for domain in $(jq -r '.DomainsCertificate.Certs[].Certificate.Domain' ${acmefile}); do 159 | # Traefik stores a cert bundle for each domain. Within this cert 160 | # bundle there is both proper the certificate and the Let's Encrypt CA 161 | echo "Extracting cert bundle for ${domain}" 162 | cert=$(jq -e -r --arg domain "$domain" '.DomainsCertificate.Certs[].Certificate | 163 | select (.Domain == $domain )| .Certificate' ${acmefile}) || bad_acme 164 | echo "${cert}" | ${CMD_DECODE_BASE64} > "${cdir}/${domain}.crt" 165 | 166 | echo "Extracting private key for ${domain}" 167 | key=$(jq -e -r --arg domain "$domain" '.DomainsCertificate.Certs[].Certificate | 168 | select (.Domain == $domain )| .PrivateKey' ${acmefile}) || bad_acme 169 | echo "${key}" | ${CMD_DECODE_BASE64} > "${pdir}/${domain}.key" 170 | done 171 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/dumpcerts.acme.v2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) 2017 Brian 'redbeard' Harrington 3 | # 4 | # dumpcerts.sh - A simple utility to explode a Traefik acme.json file into a 5 | # directory of certificates and a private key 6 | # 7 | # Usage - dumpcerts.sh /etc/traefik/acme.json /etc/ssl/ 8 | # 9 | # Dependencies - 10 | # util-linux 11 | # openssl 12 | # jq 13 | # The MIT License (MIT) 14 | # 15 | # Permission is hereby granted, free of charge, to any person obtaining a copy 16 | # of this software and associated documentation files (the "Software"), to deal 17 | # in the Software without restriction, including without limitation the rights 18 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | # copies of the Software, and to permit persons to whom the Software is 20 | # furnished to do so, subject to the following conditions: 21 | # 22 | # The above copyright notice and this permission notice shall be included in 23 | # all copies or substantial portions of the Software. 24 | # 25 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | # THE SOFTWARE. 32 | 33 | # Exit codes: 34 | # 1 - A component is missing or could not be read 35 | # 2 - There was a problem reading acme.json 36 | # 4 - The destination certificate directory does not exist 37 | # 8 - Missing private key 38 | 39 | set -o errexit 40 | set -o pipefail 41 | set -o nounset 42 | 43 | USAGE="$(basename "$0") " 44 | 45 | # Platform variations 46 | case "$(uname)" in 47 | 'Linux') 48 | # On Linux, -d should always work. --decode does not work with Alpine's busybox-binary 49 | CMD_DECODE_BASE64="base64 -d" 50 | ;; 51 | *) 52 | # Max OS-X supports --decode and -D, but --decode may be supported by other platforms as well. 53 | CMD_DECODE_BASE64="base64 --decode" 54 | ;; 55 | esac 56 | 57 | # Allow us to exit on a missing jq binary 58 | exit_jq() { 59 | echo " 60 | You must have the binary 'jq' to use this. 61 | jq is available at: https://stedolan.github.io/jq/download/ 62 | 63 | ${USAGE}" >&2 64 | exit 1 65 | } 66 | 67 | bad_acme() { 68 | echo " 69 | There was a problem parsing your acme.json file. 70 | 71 | ${USAGE}" >&2 72 | exit 2 73 | } 74 | 75 | if [ $# -ne 2 ]; then 76 | echo " 77 | Insufficient number of parameters. 78 | 79 | ${USAGE}" >&2 80 | exit 1 81 | fi 82 | 83 | readonly acmefile="${1}" 84 | readonly certdir="${2%/}" 85 | 86 | if [ ! -r "${acmefile}" ]; then 87 | echo " 88 | There was a problem reading from '${acmefile}' 89 | We need to read this file to explode the JSON bundle... exiting. 90 | 91 | ${USAGE}" >&2 92 | exit 2 93 | fi 94 | 95 | 96 | if [ ! -d "${certdir}" ]; then 97 | echo " 98 | Path ${certdir} does not seem to be a directory 99 | We need a directory in which to explode the JSON bundle... exiting. 100 | 101 | ${USAGE}" >&2 102 | exit 4 103 | fi 104 | 105 | jq=$(command -v jq) || exit_jq 106 | 107 | priv=$(${jq} -e -r '.Account.PrivateKey' "${acmefile}") || bad_acme 108 | 109 | if [ ! -n "${priv}" ]; then 110 | echo " 111 | There didn't seem to be a private key in ${acmefile}. 112 | Please ensure that there is a key in this file and try again." >&2 113 | exit 8 114 | fi 115 | 116 | # If they do not exist, create the needed subdirectories for our assets 117 | # and place each in a variable for later use, normalizing the path 118 | mkdir -p "${certdir}"/{certs,private} 119 | 120 | pdir="${certdir}/private/" 121 | cdir="${certdir}/certs/" 122 | 123 | # Save the existing umask, change the default mode to 600, then 124 | # after writing the private key switch it back to the default 125 | oldumask=$(umask) 126 | umask 177 127 | trap 'umask ${oldumask}' EXIT 128 | 129 | # traefik stores the private key in stripped base64 format but the certificates 130 | # bundled as a base64 object without stripping headers. This normalizes the 131 | # headers and formatting. 132 | # 133 | # In testing this out it was a balance between the following mechanisms: 134 | # gawk: 135 | # echo ${priv} | awk 'BEGIN {print "-----BEGIN RSA PRIVATE KEY-----"} 136 | # {gsub(/.{64}/,"&\n")}1 137 | # END {print "-----END RSA PRIVATE KEY-----"}' > "${pdir}/letsencrypt.key" 138 | # 139 | # openssl: 140 | # echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \ 141 | # | openssl rsa -inform pem -out "${pdir}/letsencrypt.key" 142 | # 143 | # and sed: 144 | # echo "-----BEGIN RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key" 145 | # echo ${priv} | sed -E 's/(.{64})/\1\n/g' >> "${pdir}/letsencrypt.key" 146 | # sed -i '$ d' "${pdir}/letsencrypt.key" 147 | # echo "-----END RSA PRIVATE KEY-----" >> "${pdir}/letsencrypt.key" 148 | # openssl rsa -noout -in "${pdir}/letsencrypt.key" -check # To check if the key is valid 149 | 150 | # In the end, openssl was chosen because most users will need this script 151 | # *because* of openssl combined with the fact that it will refuse to write the 152 | # key if it does not parse out correctly. The other mechanisms were left as 153 | # comments so that the user can choose the mechanism most appropriate to them. 154 | echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \ 155 | | openssl rsa -inform pem -out "${pdir}/letsencrypt.key" >/dev/null 156 | 157 | # Process the certificates for each of the domains in acme.json 158 | for domain in $(jq -r '.Certificates[].Domain.Main' ${acmefile}); do 159 | # Traefik stores a cert bundle for each domain. Within this cert 160 | # bundle there is both proper the certificate and the Let's Encrypt CA 161 | echo "Extracting cert bundle for ${domain}" 162 | cert=$(jq -e -r --arg domain "$domain" '.Certificates[] | 163 | select (.Domain.Main == $domain )| .Certificate' ${acmefile}) || bad_acme 164 | echo "${cert}" | ${CMD_DECODE_BASE64} > "${cdir}/${domain}.crt" 165 | 166 | echo "Extracting private key for ${domain}" 167 | key=$(jq -e -r --arg domain "$domain" '.Certificates[] | 168 | select (.Domain.Main == $domain )| .Key' ${acmefile}) || bad_acme 169 | echo "${key}" | ${CMD_DECODE_BASE64} > "${pdir}/${domain}.key" 170 | done 171 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/dumpcerts.traefik.v2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) 2017 Brian 'redbeard' Harrington 3 | # 4 | # dumpcerts.sh - A simple utility to explode a Traefik acme.json file into a 5 | # directory of certificates and a private key 6 | # 7 | # Usage - dumpcerts.sh /etc/traefik/acme.json /etc/ssl/ 8 | # 9 | # Dependencies - 10 | # util-linux 11 | # openssl 12 | # jq 13 | # The MIT License (MIT) 14 | # 15 | # Permission is hereby granted, free of charge, to any person obtaining a copy 16 | # of this software and associated documentation files (the "Software"), to deal 17 | # in the Software without restriction, including without limitation the rights 18 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | # copies of the Software, and to permit persons to whom the Software is 20 | # furnished to do so, subject to the following conditions: 21 | # 22 | # The above copyright notice and this permission notice shall be included in 23 | # all copies or substantial portions of the Software. 24 | # 25 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | # THE SOFTWARE. 32 | 33 | # Exit codes: 34 | # 1 - A component is missing or could not be read 35 | # 2 - There was a problem reading acme.json 36 | # 4 - The destination certificate directory does not exist 37 | # 8 - Missing private key 38 | 39 | set -o errexit 40 | set -o pipefail 41 | set -o nounset 42 | 43 | USAGE="$(basename "$0") " 44 | 45 | # Platform variations 46 | case "$(uname)" in 47 | 'Linux') 48 | # On Linux, -d should always work. --decode does not work with Alpine's busybox-binary 49 | CMD_DECODE_BASE64="base64 -d" 50 | ;; 51 | *) 52 | # Max OS-X supports --decode and -D, but --decode may be supported by other platforms as well. 53 | CMD_DECODE_BASE64="base64 --decode" 54 | ;; 55 | esac 56 | 57 | # Allow us to exit on a missing jq binary 58 | exit_jq() { 59 | echo " 60 | You must have the binary 'jq' to use this. 61 | jq is available at: https://stedolan.github.io/jq/download/ 62 | 63 | ${USAGE}" >&2 64 | exit 1 65 | } 66 | 67 | bad_acme() { 68 | echo " 69 | There was a problem parsing your acme.json file. 70 | 71 | ${USAGE}" >&2 72 | exit 2 73 | } 74 | 75 | if [ $# -ne 2 ]; then 76 | echo " 77 | Insufficient number of parameters. 78 | 79 | ${USAGE}" >&2 80 | exit 1 81 | fi 82 | 83 | readonly acmefile="${1}" 84 | readonly certdir="${2%/}" 85 | 86 | if [ ! -r "${acmefile}" ]; then 87 | echo " 88 | There was a problem reading from '${acmefile}' 89 | We need to read this file to explode the JSON bundle... exiting. 90 | 91 | ${USAGE}" >&2 92 | exit 2 93 | fi 94 | 95 | 96 | if [ ! -d "${certdir}" ]; then 97 | echo " 98 | Path ${certdir} does not seem to be a directory 99 | We need a directory in which to explode the JSON bundle... exiting. 100 | 101 | ${USAGE}" >&2 102 | exit 4 103 | fi 104 | 105 | jq=$(command -v jq) || exit_jq 106 | 107 | priv=$(${jq} -e -r '.[].Account.PrivateKey' "${acmefile}") || bad_acme 108 | 109 | if [ ! -n "${priv}" ]; then 110 | echo " 111 | There didn't seem to be a private key in ${acmefile}. 112 | Please ensure that there is a key in this file and try again." >&2 113 | exit 8 114 | fi 115 | 116 | # If they do not exist, create the needed subdirectories for our assets 117 | # and place each in a variable for later use, normalizing the path 118 | mkdir -p "${certdir}"/{certs,private} 119 | 120 | pdir="${certdir}/private/" 121 | cdir="${certdir}/certs/" 122 | 123 | # Save the existing umask, change the default mode to 600, then 124 | # after writing the private key switch it back to the default 125 | oldumask=$(umask) 126 | umask 177 127 | trap 'umask ${oldumask}' EXIT 128 | 129 | # traefik stores the private key in stripped base64 format but the certificates 130 | # bundled as a base64 object without stripping headers. This normalizes the 131 | # headers and formatting. 132 | # 133 | # In testing this out it was a balance between the following mechanisms: 134 | # gawk: 135 | # echo ${priv} | awk 'BEGIN {print "-----BEGIN RSA PRIVATE KEY-----"} 136 | # {gsub(/.{64}/,"&\n")}1 137 | # END {print "-----END RSA PRIVATE KEY-----"}' > "${pdir}/letsencrypt.key" 138 | # 139 | # openssl: 140 | # echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \ 141 | # | openssl rsa -inform pem -out "${pdir}/letsencrypt.key" 142 | # 143 | # and sed: 144 | # echo "-----BEGIN RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key" 145 | # echo ${priv} | sed -E 's/(.{64})/\1\n/g' >> "${pdir}/letsencrypt.key" 146 | # sed -i '$ d' "${pdir}/letsencrypt.key" 147 | # echo "-----END RSA PRIVATE KEY-----" >> "${pdir}/letsencrypt.key" 148 | # openssl rsa -noout -in "${pdir}/letsencrypt.key" -check # To check if the key is valid 149 | 150 | # In the end, openssl was chosen because most users will need this script 151 | # *because* of openssl combined with the fact that it will refuse to write the 152 | # key if it does not parse out correctly. The other mechanisms were left as 153 | # comments so that the user can choose the mechanism most appropriate to them. 154 | echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \ 155 | | openssl rsa -inform pem -out "${pdir}/letsencrypt.key" >/dev/null 156 | 157 | # Process the certificates for each of the domains in acme.json 158 | for domain in $(jq -r '.[].Certificates[].domain.main' ${acmefile}); do 159 | # Traefik stores a cert bundle for each domain. Within this cert 160 | # bundle there is both proper the certificate and the Let's Encrypt CA 161 | echo "Extracting cert bundle for ${domain}" 162 | cert=$(jq -e -r --arg domain "$domain" '.[].Certificates[] | 163 | select (.domain.main == $domain )| .certificate' ${acmefile}) || bad_acme 164 | echo "${cert}" | ${CMD_DECODE_BASE64} > "${cdir}/${domain}.crt" 165 | 166 | echo "Extracting private key for ${domain}" 167 | key=$(jq -e -r --arg domain "$domain" '.[].Certificates[] | 168 | select (.domain.main == $domain )| .key' ${acmefile}) || bad_acme 169 | echo "${key}" | ${CMD_DECODE_BASE64} > "${pdir}/${domain}.key" 170 | done -------------------------------------------------------------------------------- /rootfs/usr/local/bin/encryption.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script allow to import gpg public keys 4 | # for one or all potential recipients in the 5 | # mail server. 6 | 7 | OPTIONS=$1 8 | KEYID=$2 9 | KEYSERVER=${3:-"hkp://keys.gnupg.net"} 10 | 11 | # Zeyple home directory 12 | ZEYPLE_DIR="/var/mail/zeyple" 13 | 14 | case "$OPTIONS" in 15 | # Import a public key for one recipient 16 | "import-key") 17 | s6-setuidgid zeyple gpg --homedir "${ZEYPLE_DIR}/keys" --keyserver "${KEYSERVER}" --recv-keys "${KEYID}" 18 | ;; 19 | # Import public key for all recipients in the mailserver 20 | "import-all-keys") 21 | for dir in /var/mail/vhosts/*/ 22 | do 23 | [ -z "$(ls -A ${dir})" ] && continue 24 | dir=${dir%*/} 25 | domain=${dir##*/} 26 | for subdir in /var/mail/vhosts/${domain}/*/ 27 | do 28 | subdir=${subdir%*/} 29 | user=${subdir##*/} 30 | recipient="${user}@${domain}" 31 | s6-setuidgid zeyple gpg --homedir "${ZEYPLE_DIR}/keys" --keyserver "${KEYSERVER}" --recv-keys "${recipient}" 32 | done 33 | done 34 | ;; 35 | # Remove a public key 36 | "remove-key") 37 | s6-setuidgid zeyple gpg --homedir "${ZEYPLE_DIR}/keys" --delete-key "${KEYID}" 38 | ;; 39 | # Other GPG action 40 | *) 41 | s6-setuidgid zeyple gpg --homedir "${ZEYPLE_DIR}/keys" "$@" 42 | ;; 43 | esac 44 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/fetchmail.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use DBI; 4 | use MIME::Base64; 5 | # use Data::Dumper; 6 | use File::Temp qw/ mkstemp /; 7 | use Sys::Syslog; 8 | # require liblockfile-simple-perl 9 | use LockFile::Simple qw(lock trylock unlock); 10 | 11 | ###################################################################### 12 | ########## Change the following variables to fit your needs ########## 13 | 14 | # database settings 15 | 16 | # database backend - uncomment one of these 17 | our $db_type = 'Pg'; 18 | #my $db_type = 'mysql'; 19 | 20 | # host name 21 | our $db_host="127.0.0.1"; 22 | # database name 23 | our $db_name="postfix"; 24 | # database username 25 | our $db_username="mail"; 26 | # database password 27 | our $db_password="CHANGE_ME!"; 28 | 29 | # instead of changing this script, you can put your settings to /etc/mail/postfixadmin/fetchmail.conf 30 | # just use perl syntax there to fill the variables listed above (without the "our" keyword). Example: 31 | # $db_username = 'mail'; 32 | if (-f "/etc/postfixadmin/fetchmail.conf") { 33 | require "/etc/postfixadmin/fetchmail.conf"; 34 | } 35 | 36 | 37 | #################### Don't change anything below! #################### 38 | ###################################################################### 39 | 40 | openlog("fetchmail-all", "pid", "mail"); 41 | 42 | sub log_and_die { 43 | my($message) = @_; 44 | syslog("err", $message); 45 | die $message; 46 | } 47 | 48 | # read options and arguments 49 | 50 | $configfile = "/etc/fetchmail-all/config"; 51 | 52 | @ARGS1 = @ARGV; 53 | 54 | while ($_ = shift @ARGS1) { 55 | if (/^-/) { 56 | if (/^--config$/) { 57 | $configfile = shift @ARGS1 58 | } 59 | } 60 | } 61 | 62 | $run_dir="/var/run/fetchmail"; 63 | 64 | # use specified config file 65 | if (-e $configfile) { 66 | do $configfile; 67 | } 68 | 69 | if($db_type eq "Pg" || $db_type eq "mysql") { 70 | $dsn = "DBI:$db_type:database=$db_name;host=$db_host;port=$db_port"; 71 | } else { 72 | log_and_die "unsupported db_type $db_type"; 73 | } 74 | 75 | $lock_file=$run_dir . "/fetchmail-all.lock"; 76 | 77 | $lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1); 78 | $lockmgr->lock($lock_file) || log_and_die "can't lock ${lock_file}"; 79 | 80 | # database connect 81 | $dbh = DBI->connect($dsn, $db_username, $db_password) || log_and_die "cannot connect the database"; 82 | 83 | if($db_type eq "Pg") { 84 | $sql_cond = "active = 't' AND date_part('epoch',now())-date_part('epoch',date)"; 85 | } elsif($db_type eq "mysql") { 86 | $sql_cond = "active = 1 AND unix_timestamp(now())-unix_timestamp(date)"; 87 | } 88 | 89 | $sql = " 90 | SELECT id,mailbox,src_server,src_auth,src_user,src_password,src_folder,fetchall,keep,protocol,mda,extra_options,usessl, sslcertck, sslcertpath, sslfingerprint 91 | FROM fetchmail 92 | WHERE $sql_cond > poll_time*60 93 | "; 94 | 95 | my (%config); 96 | map{ 97 | my ($id,$mailbox,$src_server,$src_auth,$src_user,$src_password,$src_folder,$fetchall,$keep,$protocol,$mda,$extra_options,$usessl,$sslcertck,$sslcertpath,$sslfingerprint)=@$_; 98 | 99 | syslog("info","fetch ${src_user}@${src_server} for ${mailbox}"); 100 | 101 | $cmd="user '${src_user}' there with password '".decode_base64($src_password)."'"; 102 | $cmd.=" folder '${src_folder}'" if ($src_folder); 103 | $cmd.=" mda ".$mda if ($mda); 104 | 105 | # $cmd.=" mda \"/usr/local/libexec/dovecot/deliver -m ${mailbox}\""; 106 | $cmd.=" is '${mailbox}' here"; 107 | 108 | $cmd.=" keep" if ($keep); 109 | $cmd.=" fetchall" if ($fetchall); 110 | $cmd.=" ssl" if ($usessl); 111 | $cmd.=" sslcertck" if($sslcertck); 112 | $cmd.=" sslcertpath $sslcertpath" if ($sslcertck && $sslcertpath); 113 | $cmd.=" sslfingerprint \"$sslfingerprint\"" if ($sslfingerprint); 114 | $cmd.=" ".$extra_options if ($extra_options); 115 | 116 | $text=<quote($ret).", date=now() WHERE id=".$id; 137 | $dbh->do($sql); 138 | }@{$dbh->selectall_arrayref($sql)}; 139 | 140 | $lockmgr->unlock($lock_file); 141 | closelog(); 142 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/quota-warning.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PERCENT=$1 4 | USER=$2 5 | 6 | cat << EOF | /usr/lib/dovecot/dovecot-lda -d $USER -o "plugin/quota=dict:User quota::noenforcing:proxy::sqlquota" 7 | From: postmaster@{{ .DOMAIN }} 8 | Subject: Mailbox quota warning 9 | 10 | Your mailbox is now $PERCENT% full. 11 | EOF 12 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export FQDN 4 | export DOMAIN 5 | export VMAILUID 6 | export VMAILGID 7 | export VMAIL_SUBDIR 8 | 9 | export DBDRIVER 10 | export DBHOST 11 | export DBPORT 12 | export DBNAME 13 | export DBUSER 14 | 15 | export REDIS_HOST 16 | export REDIS_PORT 17 | export REDIS_PASS 18 | export REDIS_NUMB 19 | 20 | export DISABLE_CLAMAV 21 | export DISABLE_DNS_RESOLVER 22 | 23 | FQDN=${FQDN:-$(hostname --fqdn)} 24 | DOMAIN=${DOMAIN:-$(hostname --domain)} 25 | VMAILUID=${VMAILUID:-1024} 26 | VMAILGID=${VMAILGID:-1024} 27 | VMAIL_SUBDIR=${VMAIL_SUBDIR:-"mail"} 28 | 29 | DBDRIVER=${DBDRIVER:-mysql} 30 | DBHOST=${DBHOST:-mariadb} 31 | DBNAME=${DBNAME:-postfix} 32 | DBUSER=${DBUSER:-postfix} 33 | 34 | if [ "$DBDRIVER" = "ldap" ]; then 35 | DBPORT=${DBPORT:-389} 36 | else 37 | DBPORT=${DBPORT:-3306} 38 | fi 39 | 40 | REDIS_HOST=${REDIS_HOST:-redis} 41 | REDIS_PORT=${REDIS_PORT:-6379} 42 | REDIS_PASS=$([ -f "$REDIS_PASS" ] && cat "$REDIS_PASS" || echo "${REDIS_PASS:-}") 43 | REDIS_NUMB=${REDIS_NUMB:-0} 44 | 45 | DISABLE_CLAMAV=${DISABLE_CLAMAV:-false} # -- 46 | DISABLE_DNS_RESOLVER=${DISABLE_DNS_RESOLVER:-false} # -- 47 | 48 | if [ "$DBDRIVER" = "ldap" ]; then 49 | export LDAP_BIND 50 | export LDAP_BIND_DN 51 | export LDAP_BIND_PW 52 | 53 | LDAP_BIND=${LDAP_BIND:-true} 54 | LDAP_BIND_DN=${LDAP_BIND_DN:-} 55 | LDAP_BIND_PW=$([ -f "$LDAP_BIND_PW" ] && cat "$LDAP_BIND_PW" || echo "${LDAP_BIND_PW:-}") 56 | 57 | if [ "$LDAP_BIND" = true ]; then 58 | if [ -z "$LDAP_BIND_DN" ]; then 59 | echo "[ERROR] LDAP_BIND_ED must be set !" 60 | exit 1 61 | fi 62 | if [ -z "$LDAP_BIND_PW" ]; then 63 | echo "[ERROR] LDAP_BIND_PW must be set !" 64 | exit 1 65 | fi 66 | fi 67 | else 68 | if [ -z "$DBPASS" ]; then 69 | echo "[ERROR] MariaDB/PostgreSQL database password must be set !" 70 | exit 1 71 | fi 72 | fi 73 | 74 | if [ -z "$RSPAMD_PASSWORD" ]; then 75 | echo "[ERROR] Rspamd password must be set !" 76 | exit 1 77 | fi 78 | 79 | if [ -z "$FQDN" ]; then 80 | echo "[ERROR] The fully qualified domain name must be set !" 81 | exit 1 82 | fi 83 | 84 | if [ -z "$DOMAIN" ]; then 85 | echo "[ERROR] The domain name must be set !" 86 | exit 1 87 | fi 88 | 89 | # https://github.com/docker-library/redis/issues/53 90 | if [[ "$REDIS_PORT" =~ [^[:digit:]] ]]; then 91 | REDIS_PORT=6379 92 | fi 93 | 94 | # DATABASES HOSTNAME CHECKING 95 | # We need to set these in the hosts file before Unbound takes over for DNS 96 | # --------------------------------------------------------------------------------------------- 97 | 98 | # Check mariadb/postgres hostname 99 | grep -q "${DBHOST}" /etc/hosts 100 | 101 | if [ $? -ne 0 ]; then 102 | echo "[INFO] MariaDB/PostgreSQL hostname not found in /etc/hosts" 103 | IP=$(dig A ${DBHOST} +short +search) 104 | if [ -n "$IP" ]; then 105 | echo "[INFO] Container IP found, adding a new record in /etc/hosts" 106 | echo "${IP} ${DBHOST}" >> /etc/hosts 107 | else 108 | echo "[ERROR] Container IP not found with embedded DNS server... Abort !" 109 | echo "[ERROR] Check your DBHOST environment variable" 110 | exit 1 111 | fi 112 | else 113 | echo "[INFO] MariaDB/PostgreSQL hostname found in /etc/hosts" 114 | fi 115 | 116 | # Check redis hostname 117 | grep -q "${REDIS_HOST}" /etc/hosts 118 | 119 | if [ $? -ne 0 ]; then 120 | echo "[INFO] Redis hostname not found in /etc/hosts" 121 | IP=$(dig A ${REDIS_HOST} +short +search) 122 | if [ -n "$IP" ]; then 123 | echo "[INFO] Container IP found, adding a new record in /etc/hosts" 124 | echo "${IP} ${REDIS_HOST}" >> /etc/hosts 125 | else 126 | echo "[ERROR] Container IP not found with embedded DNS server... Abort !" 127 | echo "[ERROR] Check your REDIS_HOST environment variable" 128 | exit 1 129 | fi 130 | else 131 | echo "[INFO] Redis hostname found in /etc/hosts" 132 | fi 133 | 134 | # SETUP CONFIG FILES 135 | # --------------------------------------------------------------------------------------------- 136 | 137 | certs_helper.sh update_certs 138 | 139 | # Make sure that configuration is only run once 140 | if [ ! -f "/etc/configuration_built" ]; then 141 | touch "/etc/configuration_built" 142 | setup.sh 143 | fi 144 | 145 | # Unrecoverable errors detection 146 | if [ -f "/etc/setup-error" ]; then 147 | echo "[ERROR] One or more unrecoverable errors have occurred during initial setup. See above to find the cause." 148 | exit 1 149 | fi 150 | 151 | # LAUNCH ALL SERVICES 152 | # --------------------------------------------------------------------------------------------- 153 | 154 | echo "[INFO] Starting services" 155 | exec s6-svscan /services 156 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/watcher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | import time 6 | import subprocess 7 | from threading import Timer 8 | from watchdog.observers import Observer 9 | from watchdog.events import FileSystemEventHandler, RegexMatchingEventHandler 10 | 11 | 12 | def debounce(wait): 13 | """ Decorator that will postpone a functions 14 | execution until after wait seconds 15 | have elapsed since the last time it was invoked. """ 16 | def decorator(fn): 17 | def debounced(*args, **kwargs): 18 | def call_it(): 19 | fn(*args, **kwargs) 20 | try: 21 | debounced.t.cancel() 22 | except(AttributeError): 23 | pass 24 | debounced.t = Timer(wait, call_it) 25 | debounced.t.start() 26 | return debounced 27 | return decorator 28 | 29 | 30 | class CertFilesHandler(FileSystemEventHandler): 31 | def __init__(self, observer): 32 | self.observer = observer 33 | 34 | def watch(self, file_path): 35 | print("[INFO] Watching %s" % file_path) 36 | self.observer.schedule(self, file_path) 37 | 38 | def on_any_event(self, event): 39 | if event.is_directory: 40 | return 41 | print("[INFO] Watched Event %s" % repr(event)) 42 | self.reload_certificates() 43 | 44 | @debounce(3) 45 | def reload_certificates(self): 46 | status = subprocess.call(['certs_helper.sh', 'reload']) 47 | if status != 0: 48 | print("[INFO] Failed to reload certs") 49 | 50 | 51 | if __name__ == "__main__": 52 | observer = Observer() 53 | handler = CertFilesHandler(observer) 54 | for path in sys.argv[1:]: 55 | if not os.path.exists(path): 56 | print("[INFO] Skip watching %s - it does not exist" % path) 57 | continue 58 | handler.watch(path) 59 | observer.start() 60 | try: 61 | while True: 62 | time.sleep(1) 63 | except KeyboardInterrupt: 64 | observer.stop() 65 | observer.join() 66 | -------------------------------------------------------------------------------- /rootfs/usr/local/bin/zeyple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import os 6 | import logging 7 | import email 8 | import email.mime.multipart 9 | import email.mime.application 10 | import email.encoders 11 | import smtplib 12 | import copy 13 | from io import BytesIO 14 | 15 | try: 16 | from configparser import SafeConfigParser # Python 3 17 | except ImportError: 18 | from ConfigParser import SafeConfigParser # Python 2 19 | 20 | import gpgme 21 | 22 | # Boiler plate to avoid dependency on six 23 | # BBB: Python 2.7 support 24 | PY3K = sys.version_info > (3, 0) 25 | 26 | 27 | def message_from_binary(message): 28 | if PY3K: 29 | return email.message_from_bytes(message) 30 | else: 31 | return email.message_from_string(message) 32 | 33 | 34 | def as_binary_string(email): 35 | if PY3K: 36 | return email.as_bytes() 37 | else: 38 | return email.as_string() 39 | 40 | 41 | def encode_string(string): 42 | if isinstance(string, bytes): 43 | return string 44 | else: 45 | return string.encode('utf-8') 46 | 47 | 48 | __title__ = 'Zeyple' 49 | __version__ = '1.2.2' 50 | __author__ = 'Cédric Félizard' 51 | __license__ = 'AGPLv3+' 52 | __copyright__ = 'Copyright 2012-2018 Cédric Félizard' 53 | 54 | 55 | class Zeyple: 56 | """Zeyple Encrypts Your Precious Log Emails""" 57 | 58 | def __init__(self, config_fname='zeyple.conf'): 59 | self.config = self.load_configuration(config_fname) 60 | 61 | log_file = self.config.get('zeyple', 'log_file') 62 | logging.basicConfig( 63 | filename=log_file, level=logging.DEBUG, 64 | format='%(asctime)s %(process)s %(levelname)s %(message)s' 65 | ) 66 | logging.info("Zeyple ready to encrypt outgoing emails") 67 | 68 | def load_configuration(self, filename): 69 | """Reads and parses the config file""" 70 | 71 | config = SafeConfigParser() 72 | config.read([ 73 | os.path.join('/etc/zeyple/', filename), 74 | filename, 75 | ]) 76 | if not config.sections(): 77 | raise IOError('Cannot open config file.') 78 | return config 79 | 80 | @property 81 | def gpg(self): 82 | protocol = gpgme.PROTOCOL_OpenPGP 83 | 84 | if self.config.has_option('gpg', 'executable'): 85 | executable = self.config.get('gpg', 'executable') 86 | else: 87 | executable = None # Default value 88 | 89 | home_dir = self.config.get('gpg', 'home') 90 | 91 | ctx = gpgme.Context() 92 | ctx.set_engine_info(protocol, executable, home_dir) 93 | ctx.armor = True 94 | 95 | return ctx 96 | 97 | def process_message(self, message_data, recipients): 98 | """Encrypts the message with recipient keys""" 99 | message_data = encode_string(message_data) 100 | 101 | in_message = message_from_binary(message_data) 102 | logging.info( 103 | "Processing outgoing message %s", in_message['Message-id']) 104 | 105 | if not recipients: 106 | logging.warn("Cannot find any recipients, ignoring") 107 | 108 | sent_messages = [] 109 | for recipient in recipients: 110 | logging.info("Recipient: %s", recipient) 111 | 112 | key_id = self._user_key(recipient) 113 | logging.info("Key ID: %s", key_id) 114 | 115 | if key_id: 116 | out_message = self._encrypt_message(in_message, key_id) 117 | 118 | else: 119 | logging.warn("No keys found, message will be sent unencrypted") 120 | out_message = copy.copy(in_message) 121 | 122 | self._add_zeyple_header(out_message) 123 | self._send_message(out_message, recipient) 124 | sent_messages.append(out_message) 125 | 126 | return sent_messages 127 | 128 | def _get_version_part(self): 129 | ret = email.mime.application.MIMEApplication( 130 | 'Version: 1\n', 131 | 'pgp-encrypted', 132 | email.encoders.encode_noop, 133 | ) 134 | ret.add_header( 135 | 'Content-Description', 136 | "PGP/MIME version identification", 137 | ) 138 | del ret['MIME-Version'] 139 | return ret 140 | 141 | def _get_encrypted_part(self, payload): 142 | ret = email.mime.application.MIMEApplication( 143 | payload, 144 | 'octet-stream', 145 | email.encoders.encode_noop, 146 | name="encrypted.asc", 147 | ) 148 | ret.add_header('Content-Description', "OpenPGP encrypted message") 149 | ret.add_header( 150 | 'Content-Disposition', 151 | 'inline', 152 | filename='encrypted.asc', 153 | ) 154 | del ret['MIME-Version'] 155 | return ret 156 | 157 | def _encrypt_message(self, in_message, key_id): 158 | if in_message.is_multipart(): 159 | # get the body (after the first \n\n) 160 | payload = in_message.as_string().split("\n\n", 1)[1].strip() 161 | 162 | # prepend the Content-Type including the boundary 163 | content_type = "Content-Type: " + in_message["Content-Type"] 164 | payload = content_type + "\n\n" + payload 165 | 166 | message = email.message.Message() 167 | message.set_payload(payload) 168 | 169 | payload = message.get_payload() 170 | 171 | else: 172 | message = email.mime.nonmultipart.MIMENonMultipart( 173 | in_message.get_content_maintype(), 174 | in_message.get_content_subtype() 175 | ) 176 | payload = encode_string(in_message.get_payload()) 177 | message.set_payload(payload) 178 | 179 | # list of additional parameters in content-type 180 | params = in_message.get_params() 181 | if params: 182 | # first item is the main/sub type so discard it 183 | del params[0] 184 | for param, value in params: 185 | message.set_param(param, value, "Content-Type", False) 186 | 187 | encoding = in_message["Content-Transfer-Encoding"] 188 | if encoding: 189 | message.add_header("Content-Transfer-Encoding", encoding) 190 | 191 | del message['MIME-Version'] 192 | 193 | mixed = email.mime.multipart.MIMEMultipart( 194 | 'mixed', 195 | None, 196 | [message], 197 | ) 198 | 199 | # remove superfluous header 200 | del mixed['MIME-Version'] 201 | 202 | payload = as_binary_string(mixed) 203 | 204 | encrypted_payload = self._encrypt_payload(payload, [key_id]) 205 | 206 | version = self._get_version_part() 207 | encrypted = self._get_encrypted_part(encrypted_payload) 208 | 209 | out_message = copy.copy(in_message) 210 | out_message.preamble = "This is an OpenPGP/MIME encrypted " \ 211 | "message (RFC 4880 and 3156)" 212 | 213 | if 'Content-Type' not in out_message: 214 | out_message['Content-Type'] = 'multipart/encrypted' 215 | else: 216 | out_message.replace_header( 217 | 'Content-Type', 218 | 'multipart/encrypted', 219 | ) 220 | del out_message['Content-Transfer-Encoding'] 221 | out_message.set_param('protocol', 'application/pgp-encrypted') 222 | out_message.set_payload([version, encrypted]) 223 | 224 | return out_message 225 | 226 | def _encrypt_payload(self, payload, key_ids): 227 | """Encrypts the payload with the given keys""" 228 | payload = encode_string(payload) 229 | 230 | plaintext = BytesIO(payload) 231 | ciphertext = BytesIO() 232 | 233 | self.gpg.armor = True 234 | 235 | recipient = [self.gpg.get_key(key_id) for key_id in key_ids] 236 | 237 | self.gpg.encrypt(recipient, gpgme.ENCRYPT_ALWAYS_TRUST, 238 | plaintext, ciphertext) 239 | 240 | return ciphertext.getvalue() 241 | 242 | def _user_key(self, email): 243 | """Returns the GPG key for the given email address""" 244 | logging.info("Trying to encrypt for %s", email) 245 | 246 | # Explicit matching of email and uid.email necessary. 247 | # Otherwise gpg.keylist will return a list of keys 248 | # for searches like "n" 249 | for key in self.gpg.keylist(email): 250 | for uid in key.uids: 251 | if uid.email == email: 252 | return key.subkeys[0].keyid 253 | 254 | return None 255 | 256 | def _add_zeyple_header(self, message): 257 | if self.config.has_option('zeyple', 'add_header') and \ 258 | self.config.getboolean('zeyple', 'add_header'): 259 | message.add_header( 260 | 'X-Zeyple', 261 | "processed by {0} v{1}".format(__title__, __version__) 262 | ) 263 | 264 | def _send_message(self, message, recipient): 265 | """Sends the given message through the SMTP relay""" 266 | logging.info("Sending message %s", message['Message-id']) 267 | 268 | smtp = smtplib.SMTP(self.config.get('relay', 'host'), 269 | self.config.get('relay', 'port')) 270 | 271 | smtp.sendmail(message['From'], recipient, message.as_string()) 272 | smtp.quit() 273 | 274 | logging.info("Message %s sent", message['Message-id']) 275 | 276 | 277 | if __name__ == '__main__': 278 | recipients = sys.argv[1:] 279 | 280 | # BBB: Python 2.7 support 281 | binary_stdin = sys.stdin.buffer if PY3K else sys.stdin 282 | message = binary_stdin.read() 283 | 284 | zeyple = Zeyple() 285 | zeyple.process_message(message, recipients) 286 | -------------------------------------------------------------------------------- /sample.env: -------------------------------------------------------------------------------- 1 | # This file is used to define environment variables to be used 2 | # for variable substitution in your docker compose file. 3 | # https://docs.docker.com/compose/env-file/ 4 | 5 | # 6 | # MANDATORY 7 | # /!\ if this variables are unset, the mail server will not start. 8 | # 9 | 10 | # Your domain name (eg. domain.tld) 11 | MAILSERVER_DOMAIN= 12 | 13 | # MariaDB/PostgreSQL database password 14 | DATABASE_USER_PASSWORD= 15 | 16 | # Rspamd WebUI and controller password 17 | RSPAMD_PASSWORD= 18 | 19 | # 20 | # NOT MANDATORY 21 | # 22 | 23 | # Your mailserver hostname (eg. mail for mail.domain.tld) 24 | MAILSERVER_HOSTNAME=mail 25 | 26 | # Mailserver version 27 | # 1.1-stable : Stable version (v1.1-stable github branch) 28 | # 1.1-latest : Latest development build (master github branch) 29 | # Latest development builds have been validated through 30 | # the CI automation system but they are not meant for 31 | # deployment in production. 32 | # 1.1-stable and 1.1-latest are rebuilt weekly on the docker hub but if you 33 | # have any problem with a new build you can switch to 1.1-stable-fallback 34 | # The fallback tag is rebuilt monthly. 35 | MAILSERVER_DOCKER_TAG=1.1-stable 36 | 37 | # Docker volumes parent folder 38 | VOLUMES_ROOT_PATH=/mnt/docker 39 | 40 | # Docker containers restart mode 41 | # https://docs.docker.com/compose/compose-file/#restart 42 | RESTART_MODE=unless-stopped 43 | -------------------------------------------------------------------------------- /test/config/ldap/struct.ldif: -------------------------------------------------------------------------------- 1 | # LDIF Export for dc=domain,dc=tld 2 | # 3 | # 4 | 5 | version: 1 6 | 7 | # Entry 1: o=mx,dc=domain,dc=tld 8 | dn: o=mx,dc=domain,dc=tld 9 | o: mx 10 | objectclass: organization 11 | objectclass: top 12 | 13 | # Entry 2: cn=John Doe,o=mx,dc=domain,dc=tld 14 | dn: cn=John Doe,o=mx,dc=domain,dc=tld 15 | cn: John Doe 16 | givenname: John 17 | mail: john.doe@domain.tld 18 | mailalias: postmaster@domain.tld 19 | mailuserquota: 1024000 20 | objectclass: mailAccount 21 | objectclass: inetOrgPerson 22 | objectclass: top 23 | sn: Doe 24 | uid: john.doe@domain.tld 25 | userpassword: {SSHA}yOhC1Q7ATNYWB6lfThPj7+KeWPSdQR9r 26 | 27 | # Entry 3: cn=Sarah CONNOR,o=mx,dc=domain,dc=tld 28 | dn: cn=Sarah CONNOR,o=mx,dc=domain,dc=tld 29 | cn: Sarah CONNOR 30 | givenname: Sarah 31 | mail: sarah.connor@domain.tld 32 | mailuserquota: 2048000 33 | objectclass: mailAccount 34 | objectclass: inetOrgPerson 35 | objectclass: top 36 | sn: Doe 37 | uid: john.doe@domain.tld 38 | userpassword: {SSHA}yOhC1Q7ATNYWB6lfThPj7+KeWPSdQR9r 39 | 40 | # Entry 4: cn=John Connor,o=mx,dc=domain,dc=tld 41 | # Password: testpasswd2 42 | dn: cn=John Connor,o=mx,dc=domain,dc=tld 43 | cn: John Connor 44 | givenname: John 45 | mail: john.connor@domain.tld 46 | mailalias: postmaster@domain.tld 47 | mailuserquota: 4096000 48 | objectclass: mailAccount 49 | objectclass: inetOrgPerson 50 | objectclass: top 51 | sn: Connor 52 | uid: john.connor@domain.tld 53 | st: john.doe@domain.tld 54 | userpassword: {SSHA}pj0lbUl25POUi1Y0ELY/I5utY5/LKDsU 55 | 56 | # Entry 5: mailalias=hostmaster@domain.tld,o=mx,dc=domain,dc=tld 57 | dn: mailalias=hostmaster@domain.tld,o=mx,dc=domain,dc=tld 58 | mail: john.doe@domain.tld 59 | mail: john.connor@domain.tld 60 | mailalias: hostmaster@domain.tld 61 | objectclass: mailAlias 62 | objectclass: top 63 | 64 | # Entry 5: mail=group@domain.tld,o=mx,dc=domain,dc=tld 65 | dn: mail=group@domain.tld,o=mx,dc=domain,dc=tld 66 | mail: group@domain.tld 67 | objectclass: account 68 | objectclass: mailGroup 69 | objectclass: top 70 | uid: john.doe@domain.tld 71 | -------------------------------------------------------------------------------- /test/config/mariadb/bind.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | 3 | bind-address = 0.0.0.0 4 | -------------------------------------------------------------------------------- /test/config/mariadb/struct.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.16 Distrib 10.2.14-MariaDB, for debian-linux-gnu (x86_64) 2 | -- 3 | -- Host: localhost Database: postfix 4 | -- ------------------------------------------------------ 5 | -- Server version 10.2.14-MariaDB-10.2.14+maria~jessie 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `admin` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `admin`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `admin` ( 26 | `username` varchar(255) NOT NULL, 27 | `password` varchar(255) NOT NULL, 28 | `superadmin` tinyint(1) NOT NULL DEFAULT 0, 29 | `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 30 | `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 31 | `active` tinyint(1) NOT NULL DEFAULT 1, 32 | `phone` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '', 33 | `email_other` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '', 34 | `token` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '', 35 | `token_validity` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 36 | PRIMARY KEY (`username`) 37 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Postfix Admin - Virtual Admins'; 38 | /*!40101 SET character_set_client = @saved_cs_client */; 39 | 40 | -- 41 | -- Dumping data for table `admin` 42 | -- 43 | 44 | LOCK TABLES `admin` WRITE; 45 | /*!40000 ALTER TABLE `admin` DISABLE KEYS */; 46 | INSERT INTO `admin` VALUES ('admin@domain.tld','{SHA512-CRYPT}$6$Wt7uQEnB6HPP6mM0$lOP8IKtEUJKWSwczEC5/g6aYamkwh5rx3ztnRuqcRLJjGTXiLpUnxzUgy2rfNieH9C8x7M6Nr9q19SG6njUj//',1,'2016-11-28 08:53:31','2016-11-28 08:53:31',1,'','','','2000-01-01 00:00:00'); 47 | /*!40000 ALTER TABLE `admin` ENABLE KEYS */; 48 | UNLOCK TABLES; 49 | 50 | -- 51 | -- Table structure for table `alias` 52 | -- 53 | 54 | DROP TABLE IF EXISTS `alias`; 55 | /*!40101 SET @saved_cs_client = @@character_set_client */; 56 | /*!40101 SET character_set_client = utf8 */; 57 | CREATE TABLE `alias` ( 58 | `address` varchar(255) NOT NULL, 59 | `goto` text NOT NULL, 60 | `domain` varchar(255) NOT NULL, 61 | `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 62 | `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 63 | `active` tinyint(1) NOT NULL DEFAULT 1, 64 | PRIMARY KEY (`address`), 65 | KEY `domain` (`domain`) 66 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Postfix Admin - Virtual Aliases'; 67 | /*!40101 SET character_set_client = @saved_cs_client */; 68 | 69 | -- 70 | -- Dumping data for table `alias` 71 | -- 72 | 73 | LOCK TABLES `alias` WRITE; 74 | /*!40000 ALTER TABLE `alias` DISABLE KEYS */; 75 | INSERT INTO `alias` VALUES ('postmaster@domain.tld','john.doe@domain.tld','domain.tld','2016-11-28 08:54:26','2016-11-28 08:58:19',1),('hostmaster@domain.tld','john.doe@domain.tld','domain.tld','2016-11-28 08:54:26','2016-11-28 08:58:19',1),('john.doe@domain.tld','john.doe@domain.tld','domain.tld','2016-11-28 08:56:47','2016-11-28 08:56:47',1),('sarah.connor@domain.tld','sarah.connor@domain.tld','domain.tld','2016-11-28 08:57:51','2016-11-28 08:57:51',1); 76 | /*!40000 ALTER TABLE `alias` ENABLE KEYS */; 77 | UNLOCK TABLES; 78 | 79 | -- 80 | -- Table structure for table `alias_domain` 81 | -- 82 | 83 | DROP TABLE IF EXISTS `alias_domain`; 84 | /*!40101 SET @saved_cs_client = @@character_set_client */; 85 | /*!40101 SET character_set_client = utf8 */; 86 | CREATE TABLE `alias_domain` ( 87 | `alias_domain` varchar(255) NOT NULL DEFAULT '', 88 | `target_domain` varchar(255) NOT NULL DEFAULT '', 89 | `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 90 | `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 91 | `active` tinyint(1) NOT NULL DEFAULT 1, 92 | PRIMARY KEY (`alias_domain`), 93 | KEY `active` (`active`), 94 | KEY `target_domain` (`target_domain`) 95 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Postfix Admin - Domain Aliases'; 96 | /*!40101 SET character_set_client = @saved_cs_client */; 97 | 98 | -- 99 | -- Dumping data for table `alias_domain` 100 | -- 101 | 102 | LOCK TABLES `alias_domain` WRITE; 103 | /*!40000 ALTER TABLE `alias_domain` DISABLE KEYS */; 104 | /*!40000 ALTER TABLE `alias_domain` ENABLE KEYS */; 105 | UNLOCK TABLES; 106 | 107 | -- 108 | -- Table structure for table `config` 109 | -- 110 | 111 | DROP TABLE IF EXISTS `config`; 112 | /*!40101 SET @saved_cs_client = @@character_set_client */; 113 | /*!40101 SET character_set_client = utf8 */; 114 | CREATE TABLE `config` ( 115 | `id` int(11) NOT NULL AUTO_INCREMENT, 116 | `name` varchar(20) NOT NULL DEFAULT '', 117 | `value` varchar(20) NOT NULL DEFAULT '', 118 | PRIMARY KEY (`id`), 119 | UNIQUE KEY `name` (`name`) 120 | ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 COMMENT='PostfixAdmin settings'; 121 | /*!40101 SET character_set_client = @saved_cs_client */; 122 | 123 | -- 124 | -- Dumping data for table `config` 125 | -- 126 | 127 | LOCK TABLES `config` WRITE; 128 | /*!40000 ALTER TABLE `config` DISABLE KEYS */; 129 | INSERT INTO `config` VALUES (1,'version','1840'); 130 | /*!40000 ALTER TABLE `config` ENABLE KEYS */; 131 | UNLOCK TABLES; 132 | 133 | -- 134 | -- Table structure for table `domain` 135 | -- 136 | 137 | DROP TABLE IF EXISTS `domain`; 138 | /*!40101 SET @saved_cs_client = @@character_set_client */; 139 | /*!40101 SET character_set_client = utf8 */; 140 | CREATE TABLE `domain` ( 141 | `domain` varchar(255) NOT NULL, 142 | `description` varchar(255) CHARACTER SET utf8 NOT NULL, 143 | `aliases` int(10) NOT NULL DEFAULT 0, 144 | `mailboxes` int(10) NOT NULL DEFAULT 0, 145 | `maxquota` bigint(20) NOT NULL DEFAULT 0, 146 | `quota` bigint(20) NOT NULL DEFAULT 0, 147 | `transport` varchar(255) NOT NULL, 148 | `backupmx` tinyint(1) NOT NULL DEFAULT 0, 149 | `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 150 | `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 151 | `active` tinyint(1) NOT NULL DEFAULT 1, 152 | PRIMARY KEY (`domain`) 153 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Postfix Admin - Virtual Domains'; 154 | /*!40101 SET character_set_client = @saved_cs_client */; 155 | 156 | -- 157 | -- Dumping data for table `domain` 158 | -- 159 | 160 | LOCK TABLES `domain` WRITE; 161 | /*!40000 ALTER TABLE `domain` DISABLE KEYS */; 162 | INSERT INTO `domain` VALUES ('ALL','',0,0,0,0,'',0,'2016-11-28 08:53:31','2016-11-28 08:53:31',1),('domain.tld','Test domain',0,0,1,0,'virtual',0,'2016-11-28 08:54:26','2016-11-28 08:54:26',1); 163 | /*!40000 ALTER TABLE `domain` ENABLE KEYS */; 164 | UNLOCK TABLES; 165 | 166 | -- 167 | -- Table structure for table `domain_admins` 168 | -- 169 | 170 | DROP TABLE IF EXISTS `domain_admins`; 171 | /*!40101 SET @saved_cs_client = @@character_set_client */; 172 | /*!40101 SET character_set_client = utf8 */; 173 | CREATE TABLE `domain_admins` ( 174 | `username` varchar(255) NOT NULL, 175 | `domain` varchar(255) NOT NULL, 176 | `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 177 | `active` tinyint(1) NOT NULL DEFAULT 1, 178 | KEY `username` (`username`) 179 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Postfix Admin - Domain Admins'; 180 | /*!40101 SET character_set_client = @saved_cs_client */; 181 | 182 | -- 183 | -- Dumping data for table `domain_admins` 184 | -- 185 | 186 | LOCK TABLES `domain_admins` WRITE; 187 | /*!40000 ALTER TABLE `domain_admins` DISABLE KEYS */; 188 | INSERT INTO `domain_admins` VALUES ('admin@domain.tld','ALL','2016-11-28 08:53:31',1); 189 | /*!40000 ALTER TABLE `domain_admins` ENABLE KEYS */; 190 | UNLOCK TABLES; 191 | 192 | -- 193 | -- Table structure for table `fetchmail` 194 | -- 195 | 196 | DROP TABLE IF EXISTS `fetchmail`; 197 | /*!40101 SET @saved_cs_client = @@character_set_client */; 198 | /*!40101 SET character_set_client = utf8 */; 199 | CREATE TABLE `fetchmail` ( 200 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 201 | `domain` varchar(255) DEFAULT '', 202 | `mailbox` varchar(255) NOT NULL, 203 | `src_server` varchar(255) NOT NULL, 204 | `src_auth` enum('password','kerberos_v5','kerberos','kerberos_v4','gssapi','cram-md5','otp','ntlm','msn','ssh','any') DEFAULT NULL, 205 | `src_user` varchar(255) NOT NULL, 206 | `src_password` varchar(255) NOT NULL, 207 | `src_folder` varchar(255) NOT NULL, 208 | `poll_time` int(11) unsigned NOT NULL DEFAULT 10, 209 | `fetchall` tinyint(1) unsigned NOT NULL DEFAULT 0, 210 | `keep` tinyint(1) unsigned NOT NULL DEFAULT 0, 211 | `protocol` enum('POP3','IMAP','POP2','ETRN','AUTO') DEFAULT NULL, 212 | `usessl` tinyint(1) unsigned NOT NULL DEFAULT 0, 213 | `sslcertck` tinyint(1) NOT NULL DEFAULT 0, 214 | `sslcertpath` varchar(255) CHARACTER SET utf8 DEFAULT '', 215 | `sslfingerprint` varchar(255) DEFAULT '', 216 | `extra_options` text DEFAULT NULL, 217 | `returned_text` text DEFAULT NULL, 218 | `mda` varchar(255) NOT NULL, 219 | `date` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', 220 | `created` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', 221 | `modified` timestamp NOT NULL DEFAULT current_timestamp(), 222 | `active` tinyint(1) NOT NULL DEFAULT 0, 223 | PRIMARY KEY (`id`) 224 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 225 | /*!40101 SET character_set_client = @saved_cs_client */; 226 | 227 | -- 228 | -- Dumping data for table `fetchmail` 229 | -- 230 | 231 | LOCK TABLES `fetchmail` WRITE; 232 | /*!40000 ALTER TABLE `fetchmail` DISABLE KEYS */; 233 | INSERT INTO `fetchmail` VALUES (1,'domain.tld','sarah.connor@domain.tld','127.0.0.1','password','john.doe@domain.tld','dGVzdHBhc3N3ZDEy','',10,1,1,'IMAP',1,0,'','','','','','2016-12-05 11:59:01','2016-12-05 11:58:53','2016-12-05 11:58:53',1); 234 | /*!40000 ALTER TABLE `fetchmail` ENABLE KEYS */; 235 | UNLOCK TABLES; 236 | 237 | -- 238 | -- Table structure for table `log` 239 | -- 240 | 241 | DROP TABLE IF EXISTS `log`; 242 | /*!40101 SET @saved_cs_client = @@character_set_client */; 243 | /*!40101 SET character_set_client = utf8 */; 244 | CREATE TABLE `log` ( 245 | `timestamp` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 246 | `username` varchar(255) NOT NULL, 247 | `domain` varchar(255) NOT NULL, 248 | `action` varchar(255) NOT NULL, 249 | `data` text NOT NULL, 250 | `id` int(11) NOT NULL AUTO_INCREMENT, 251 | PRIMARY KEY (`id`), 252 | KEY `timestamp` (`timestamp`), 253 | KEY `domain_timestamp` (`domain`,`timestamp`) 254 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Postfix Admin - Log'; 255 | /*!40101 SET character_set_client = @saved_cs_client */; 256 | 257 | -- 258 | -- Dumping data for table `log` 259 | -- 260 | 261 | LOCK TABLES `log` WRITE; 262 | /*!40000 ALTER TABLE `log` DISABLE KEYS */; 263 | /*!40000 ALTER TABLE `log` ENABLE KEYS */; 264 | UNLOCK TABLES; 265 | 266 | -- 267 | -- Table structure for table `mailbox` 268 | -- 269 | 270 | DROP TABLE IF EXISTS `mailbox`; 271 | /*!40101 SET @saved_cs_client = @@character_set_client */; 272 | /*!40101 SET character_set_client = utf8 */; 273 | CREATE TABLE `mailbox` ( 274 | `username` varchar(255) NOT NULL, 275 | `password` varchar(255) NOT NULL, 276 | `name` varchar(255) CHARACTER SET utf8 NOT NULL, 277 | `maildir` varchar(255) NOT NULL, 278 | `quota` bigint(20) NOT NULL DEFAULT 0, 279 | `local_part` varchar(255) NOT NULL, 280 | `domain` varchar(255) NOT NULL, 281 | `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 282 | `modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 283 | `active` tinyint(1) NOT NULL DEFAULT 1, 284 | `phone` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '', 285 | `email_other` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '', 286 | `token` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '', 287 | `token_validity` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 288 | PRIMARY KEY (`username`), 289 | KEY `domain` (`domain`) 290 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Postfix Admin - Virtual Mailboxes'; 291 | /*!40101 SET character_set_client = @saved_cs_client */; 292 | 293 | -- 294 | -- Dumping data for table `mailbox` 295 | -- 296 | 297 | LOCK TABLES `mailbox` WRITE; 298 | /*!40000 ALTER TABLE `mailbox` DISABLE KEYS */; 299 | INSERT INTO `mailbox` VALUES ('john.doe@domain.tld','{SHA512-CRYPT}$6$v1LkarodHyGGmfoy$ZszVBzfEZ0CaVnYaBasgvaHJUCNfxwD/E0eNy3iuix56Vl1ZcuDvG9PVr9JRZx5k.7wp1nMb5M1V4aZXo2yfn0','John DOE','domain.tld/john.doe/',1024000,'john.doe','domain.tld','2016-11-28 08:56:47','2016-11-28 08:56:47',1,'','','','2000-01-01 00:00:00'),('sarah.connor@domain.tld','{SHA512-CRYPT}$6$ub.zCcyeaM7Mhs6S$rL4Yj2.Zsk8aFoF5l1mAddVrPo.UZ/1UrNwBC7UTBrX47cViSHo5eepEes6jMqC21P3cBm82adqJZvo91Ekme0','Sarah CONNOR','domain.tld/sarah.connor/',1024000,'sarah.connor','domain.tld','2016-11-28 08:57:51','2016-11-28 08:57:51',1,'','','','2000-01-01 00:00:00'); 300 | /*!40000 ALTER TABLE `mailbox` ENABLE KEYS */; 301 | UNLOCK TABLES; 302 | 303 | -- 304 | -- Table structure for table `quota` 305 | -- 306 | 307 | DROP TABLE IF EXISTS `quota`; 308 | /*!40101 SET @saved_cs_client = @@character_set_client */; 309 | /*!40101 SET character_set_client = utf8 */; 310 | CREATE TABLE `quota` ( 311 | `username` varchar(255) NOT NULL, 312 | `path` varchar(100) NOT NULL, 313 | `current` bigint(20) NOT NULL DEFAULT 0, 314 | PRIMARY KEY (`username`,`path`) 315 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1; 316 | /*!40101 SET character_set_client = @saved_cs_client */; 317 | 318 | -- 319 | -- Dumping data for table `quota` 320 | -- 321 | 322 | LOCK TABLES `quota` WRITE; 323 | /*!40000 ALTER TABLE `quota` DISABLE KEYS */; 324 | /*!40000 ALTER TABLE `quota` ENABLE KEYS */; 325 | UNLOCK TABLES; 326 | 327 | -- 328 | -- Table structure for table `quota2` 329 | -- 330 | 331 | DROP TABLE IF EXISTS `quota2`; 332 | /*!40101 SET @saved_cs_client = @@character_set_client */; 333 | /*!40101 SET character_set_client = utf8 */; 334 | CREATE TABLE `quota2` ( 335 | `username` varchar(100) NOT NULL, 336 | `bytes` bigint(20) NOT NULL DEFAULT 0, 337 | `messages` int(11) NOT NULL DEFAULT 0, 338 | PRIMARY KEY (`username`) 339 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1; 340 | /*!40101 SET character_set_client = @saved_cs_client */; 341 | 342 | -- 343 | -- Dumping data for table `quota2` 344 | -- 345 | 346 | LOCK TABLES `quota2` WRITE; 347 | /*!40000 ALTER TABLE `quota2` DISABLE KEYS */; 348 | INSERT INTO `quota2` VALUES ('john.doe@domain.tld',0,0),('sarah.connor@domain.tld',0,0); 349 | /*!40000 ALTER TABLE `quota2` ENABLE KEYS */; 350 | UNLOCK TABLES; 351 | 352 | -- 353 | -- Table structure for table `vacation` 354 | -- 355 | 356 | DROP TABLE IF EXISTS `vacation`; 357 | /*!40101 SET @saved_cs_client = @@character_set_client */; 358 | /*!40101 SET character_set_client = utf8 */; 359 | CREATE TABLE `vacation` ( 360 | `email` varchar(255) NOT NULL, 361 | `subject` varchar(255) CHARACTER SET utf8 NOT NULL, 362 | `body` text CHARACTER SET utf8 NOT NULL, 363 | `activefrom` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', 364 | `activeuntil` timestamp NOT NULL DEFAULT '2038-01-18 00:00:00', 365 | `cache` text NOT NULL, 366 | `domain` varchar(255) NOT NULL, 367 | `interval_time` int(11) NOT NULL DEFAULT 0, 368 | `created` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 369 | `modified` timestamp NOT NULL DEFAULT current_timestamp(), 370 | `active` tinyint(1) NOT NULL DEFAULT 1, 371 | PRIMARY KEY (`email`), 372 | KEY `email` (`email`) 373 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Postfix Admin - Virtual Vacation'; 374 | /*!40101 SET character_set_client = @saved_cs_client */; 375 | 376 | -- 377 | -- Dumping data for table `vacation` 378 | -- 379 | 380 | LOCK TABLES `vacation` WRITE; 381 | /*!40000 ALTER TABLE `vacation` DISABLE KEYS */; 382 | /*!40000 ALTER TABLE `vacation` ENABLE KEYS */; 383 | UNLOCK TABLES; 384 | 385 | -- 386 | -- Table structure for table `vacation_notification` 387 | -- 388 | 389 | DROP TABLE IF EXISTS `vacation_notification`; 390 | /*!40101 SET @saved_cs_client = @@character_set_client */; 391 | /*!40101 SET character_set_client = utf8 */; 392 | CREATE TABLE `vacation_notification` ( 393 | `on_vacation` varchar(255) CHARACTER SET latin1 NOT NULL, 394 | `notified` varchar(255) CHARACTER SET latin1 NOT NULL DEFAULT '', 395 | `notified_at` timestamp NOT NULL DEFAULT current_timestamp(), 396 | PRIMARY KEY (`on_vacation`,`notified`), 397 | CONSTRAINT `vacation_notification_pkey` FOREIGN KEY (`on_vacation`) REFERENCES `vacation` (`email`) ON DELETE CASCADE 398 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Postfix Admin - Virtual Vacation Notifications'; 399 | /*!40101 SET character_set_client = @saved_cs_client */; 400 | 401 | -- 402 | -- Dumping data for table `vacation_notification` 403 | -- 404 | 405 | LOCK TABLES `vacation_notification` WRITE; 406 | /*!40000 ALTER TABLE `vacation_notification` DISABLE KEYS */; 407 | /*!40000 ALTER TABLE `vacation_notification` ENABLE KEYS */; 408 | UNLOCK TABLES; 409 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 410 | 411 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 412 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 413 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 414 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 415 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 416 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 417 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 418 | 419 | -- Dump completed on 2018-05-15 6:53:36 420 | -------------------------------------------------------------------------------- /test/share/clamav/unofficial-sigs/user.conf: -------------------------------------------------------------------------------- 1 | user_configuration_complete="yes" 2 | -------------------------------------------------------------------------------- /test/share/dovecot/conf.d/10-custom.conf: -------------------------------------------------------------------------------- 1 | login_greeting = Do. Or do not. There is no try. 2 | -------------------------------------------------------------------------------- /test/share/dovecot/conf.d/20-custom.conf: -------------------------------------------------------------------------------- 1 | protocol imap { 2 | 3 | mail_max_userip_connections = 100 4 | 5 | } 6 | 7 | protocol pop3 { 8 | 9 | mail_max_userip_connections = 50 10 | 11 | } 12 | 13 | -------------------------------------------------------------------------------- /test/share/letsencrypt/live/mail.domain.tld/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFGTCCBAGgAwIBAgISA50jj6A/ilExMla41PwSejyBMA0GCSqGSIb3DQEBCwUA 3 | MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD 4 | ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xNjA0MTkxOTA1MDBaFw0x 5 | NjA3MTgxOTA1MDBaMBUxEzARBgNVBAMTCmlmdXNpby5jb20wggEiMA0GCSqGSIb3 6 | DQEBAQUAA4IBDwAwggEKAoIBAQCcClf+Pee1EItdnjagOUQuwA4SLLiKCf5T+2Ec 7 | BPnwMGKtDb/TBWc8KEHQGxYCdtamFciT+OXUlJGjPGEna4DAKANi5njmq+TQFb7J 8 | ipA7pfQ4fp/2OqG3e6SwNvWurJlHIigiLe1lbc+7rt/5hon7Jwn260x/XaPHXRkU 9 | Aiy5FSDVeXnnCL5QOu5srnHrdTlWpEnz9WUvYCj3DMR38gxojnmpj48aMRRtrBAO 10 | NlxT9TssHoKvDXI1bEbeb2tpmC/+kRPusIukiucc3Fo9R/sHXjFkD7mK2UMb0ULE 11 | BG2D4wwEINUSG3B3wsu0eywAlkpX1UcFzdFTtsjU7V2a06jBAgMBAAGjggIsMIIC 12 | KDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC 13 | MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFOKqRnKTd2adWD+SndSZVFPsLVJkMB8G 14 | A1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMHAGCCsGAQUFBwEBBGQwYjAv 15 | BggrBgEFBQcwAYYjaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0Lm9yZy8w 16 | LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv 17 | MDYGA1UdEQQvMC2CCmlmdXNpby5jb22CD21haWwuaWZ1c2lvLmNvbYIOd3d3Lmlm 18 | dXNpby5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEB 19 | MIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYI 20 | KwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGll 21 | ZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNl 22 | IHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xl 23 | dHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAgzf9 24 | DVCdVtKvlmGeT2puHU5ULf3t/JD6NL3ocuBMsDQPOHxa6kkyd6xqdBAelNSfEYv+ 25 | BVfQp6Wox2IGrwfqqvNNzPGTHLxSpK94Gk0eeg7YhcxjoOryv4FgowQOax5J0OSS 26 | WIdAFVykPs87WKyHNY8W1zle/Ye9yjS6bjHdjqnOiG/7qDQ/DDYGn7ILHAHmUZYy 27 | 1QQ0EdffNkLpkmCnTnotgBUpqmDt7pMNZRuYFTQq631ihe7jRXjSkgWS7tTfUT15 28 | SesUIo1NbjCJmBceFd2c/srgVlbWc2LXt7Qf5yxWJyhT16r/M7ok0btH25D5azk2 29 | TKdnq/QFhHWVZUr3hg== 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /test/share/letsencrypt/live/mail.domain.tld/chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ 3 | MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT 4 | DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow 5 | SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT 6 | GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC 7 | AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF 8 | q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 9 | SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 10 | Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA 11 | a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj 12 | /PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T 13 | AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG 14 | CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv 15 | bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k 16 | c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw 17 | VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC 18 | ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz 19 | MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu 20 | Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF 21 | AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo 22 | uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ 23 | wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu 24 | X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG 25 | PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 26 | KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /test/share/letsencrypt/live/mail.domain.tld/fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFGTCCBAGgAwIBAgISA50jj6A/ilExMla41PwSejyBMA0GCSqGSIb3DQEBCwUA 3 | MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD 4 | ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xNjA0MTkxOTA1MDBaFw0x 5 | NjA3MTgxOTA1MDBaMBUxEzARBgNVBAMTCmlmdXNpby5jb20wggEiMA0GCSqGSIb3 6 | DQEBAQUAA4IBDwAwggEKAoIBAQCcClf+Pee1EItdnjagOUQuwA4SLLiKCf5T+2Ec 7 | BPnwMGKtDb/TBWc8KEHQGxYCdtamFciT+OXUlJGjPGEna4DAKANi5njmq+TQFb7J 8 | ipA7pfQ4fp/2OqG3e6SwNvWurJlHIigiLe1lbc+7rt/5hon7Jwn260x/XaPHXRkU 9 | Aiy5FSDVeXnnCL5QOu5srnHrdTlWpEnz9WUvYCj3DMR38gxojnmpj48aMRRtrBAO 10 | NlxT9TssHoKvDXI1bEbeb2tpmC/+kRPusIukiucc3Fo9R/sHXjFkD7mK2UMb0ULE 11 | BG2D4wwEINUSG3B3wsu0eywAlkpX1UcFzdFTtsjU7V2a06jBAgMBAAGjggIsMIIC 12 | KDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC 13 | MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFOKqRnKTd2adWD+SndSZVFPsLVJkMB8G 14 | A1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMHAGCCsGAQUFBwEBBGQwYjAv 15 | BggrBgEFBQcwAYYjaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0Lm9yZy8w 16 | LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv 17 | MDYGA1UdEQQvMC2CCmlmdXNpby5jb22CD21haWwuaWZ1c2lvLmNvbYIOd3d3Lmlm 18 | dXNpby5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEB 19 | MIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYI 20 | KwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGll 21 | ZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNl 22 | IHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xl 23 | dHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAgzf9 24 | DVCdVtKvlmGeT2puHU5ULf3t/JD6NL3ocuBMsDQPOHxa6kkyd6xqdBAelNSfEYv+ 25 | BVfQp6Wox2IGrwfqqvNNzPGTHLxSpK94Gk0eeg7YhcxjoOryv4FgowQOax5J0OSS 26 | WIdAFVykPs87WKyHNY8W1zle/Ye9yjS6bjHdjqnOiG/7qDQ/DDYGn7ILHAHmUZYy 27 | 1QQ0EdffNkLpkmCnTnotgBUpqmDt7pMNZRuYFTQq631ihe7jRXjSkgWS7tTfUT15 28 | SesUIo1NbjCJmBceFd2c/srgVlbWc2LXt7Qf5yxWJyhT16r/M7ok0btH25D5azk2 29 | TKdnq/QFhHWVZUr3hg== 30 | -----END CERTIFICATE----- 31 | -----BEGIN CERTIFICATE----- 32 | MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ 33 | MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT 34 | DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow 35 | SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT 36 | GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC 37 | AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF 38 | q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 39 | SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 40 | Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA 41 | a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj 42 | /PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T 43 | AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG 44 | CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv 45 | bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k 46 | c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw 47 | VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC 48 | ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz 49 | MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu 50 | Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF 51 | AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo 52 | uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ 53 | wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu 54 | X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG 55 | PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 56 | KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== 57 | -----END CERTIFICATE----- 58 | -------------------------------------------------------------------------------- /test/share/letsencrypt/live/mail.domain.tld/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCcClf+Pee1EItd 3 | njagOUQuwA4SLLiKCf5T+2EcBPnwMGKtDb/TBWc8KEHQGxYCdtamFciT+OXUlJGj 4 | PGEna4DAKANi5njmq+TQFb7JipA7pfQ4fp/2OqG3e6SwNvWurJlHIigiLe1lbc+7 5 | rt/5hon7Jwn260x/XaPHXRkUAiy5FSDVeXnnCL5QOu5srnHrdTlWpEnz9WUvYCj3 6 | DMR38gxojnmpj48aMRRtrBAONlxT9TssHoKvDXI1bEbeb2tpmC/+kRPusIukiucc 7 | 3Fo9R/sHXjFkD7mK2UMb0ULEBG2D4wwEINUSG3B3wsu0eywAlkpX1UcFzdFTtsjU 8 | 7V2a06jBAgMBAAECggEAM+cA49FljAWHxckFdH/33PEG/Sag71FppjecUnyZQjpl 9 | 6BgFsUQ/1XOyiG0qAgHTXgUq5YVJtU8BrmE8E6efeMsWbUQp/Ng6ULia8GDFnwGR 10 | XWVJAdb4yZY37mEpkUNZ7J5A6TWLnExNZ6lAWLhWlxKiLt6PYGIeQwcFe3FJvPn0 11 | GUSFYXYLJq9lIirQvl6lVzljpP6qvFQEqa4dgWqBOrC2/jL4amKK71N23sFnz0sQ 12 | UqjP72c7Nr+4xMOhsGetV1mYgPqAoR5W3XZRqDPeZ6f0FJ7L5xG69YJeo7MrXhKl 13 | N9kLhbGR/FH3IHdo7tRb2oGmHDLNJf9z/mBEerSW0QKBgQDPHCc59tCwmF1UV4eV 14 | fhRoq4Qyp8wF2ItJTMd12LJOX67eNJy6v93AhuaPgKp/kRMul5ZjL90UV1ISl/lz 15 | eYzgreMttCtRhZxUSvBo6w32NSacoiKcZojyTdyZPuOeAG2RxNHh+n+NZ7NjIWvG 16 | 7xlW9HXQOPi8xlHpDfDs+7EgXQKBgQDA4AJlor22dExGuDOLlO0et+TVhErqXFSZ 17 | CtRmwsL+XTeSKyngZcyu1YnyoNOA9vGyOk26AW+WV9N9U6rwVs8CVczee5eKPZJN 18 | xQZjUo+fep3xIFvBpgnadfrodxjD8XMpHcb0I/ZRICZKqIVv9q+FzEXqE+HsfD3B 19 | kBfZ8GZztQKBgEVSdw6/vjpdxV9lrMws10fxoN4TrAaI5JY0TM71KTlybWWS1qLr 20 | dZ3riWCfAHKSbIk70+p/KtCUKbRvid9M4AqUKWYy2A0BW8IbEz0K8DFouPPUkSEo 21 | cM4poZzpn+ZS3lncNyQcZHVAMJsNpLWBcknYqVZ4u0j0WJZZRDsOQ8tBAoGBAK2p 22 | BH9+iFI/ZG5IbCDBdr6x1NhqxQk/GOyzU4sy0V81j1OMiagCAMlqe0p6g/UaY4SV 23 | +mX/5Pj5GvM84iyD/N+dYVjw7wEJbzGWtKm5LJfrT0pMWFGDrluE3uVwVlwWihn7 24 | NaecuatRxyhxk7O76U4PHuQkAsdrFi+yDcetLJIBAoGBAJHUMttKQ9/sc6EYgdym 25 | u8hMi/WGrt5eOOAJ17lY53eRZLci7s1mfsWIF9b0N50iE60SaFADQiMRAUtkJXNI 26 | a55qdpalVHsAE4Wwh7nlKLkaDEartx5X1qSTFw4fTMyKNOveiggQ/i9LZpFxsz22 27 | 3V+7jPJaCNyPbmOevXGhBEjr 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/share/passwd/mariadb: -------------------------------------------------------------------------------- 1 | testpasswd 2 | -------------------------------------------------------------------------------- /test/share/passwd/postgres: -------------------------------------------------------------------------------- 1 | testpasswd 2 | -------------------------------------------------------------------------------- /test/share/passwd/redis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hardware/mailserver/0b42720eb65f98c213862ed9dec11f2a8c1c7da3/test/share/passwd/redis -------------------------------------------------------------------------------- /test/share/passwd/rspamd: -------------------------------------------------------------------------------- 1 | testpasswd 2 | -------------------------------------------------------------------------------- /test/share/postfix/custom.conf: -------------------------------------------------------------------------------- 1 | max_idle = 600s 2 | readme_directory = /tmp 3 | P|submission/inet/syslog_name=postfix/submission-custom 4 | -------------------------------------------------------------------------------- /test/share/postfix/custom.ecdsa.conf: -------------------------------------------------------------------------------- 1 | tls_eecdh_strong_curve = secp384r1 2 | -------------------------------------------------------------------------------- /test/share/postfix/sender_access: -------------------------------------------------------------------------------- 1 | john.doe@domain.tld REJECT 2 | -------------------------------------------------------------------------------- /test/share/sieve/custom.sieve: -------------------------------------------------------------------------------- 1 | if header :contains "X-Spam-Level" "**********" { 2 | discard; 3 | stop; 4 | } 5 | -------------------------------------------------------------------------------- /test/share/ssl/ecdsa/selfsigned/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICvzCCAkWgAwIBAgIJAIAkd9z1YBD7MAoGCCqGSM49BAMCMIGbMQswCQYDVQQG 3 | EwJGUjEPMA0GA1UECAwGRnJhbmNlMQ4wDAYDVQQHDAVQYXJpczEfMB0GA1UECgwW 4 | TWFpbHNlcnZlciBjZXJ0aWZpY2F0ZTENMAsGA1UECwwETWFpbDEVMBMGA1UEAwwM 5 | Ki5kb21haW4udGxkMSQwIgYJKoZIhvcNAQkBFhVwb3N0bWFzdGVyQGRvbWFpbi50 6 | bGQwHhcNMTcwODI4MDkwODI4WhcNMjcwOTAzMDkwODI4WjCBmzELMAkGA1UEBhMC 7 | RlIxDzANBgNVBAgMBkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxHzAdBgNVBAoMFk1h 8 | aWxzZXJ2ZXIgY2VydGlmaWNhdGUxDTALBgNVBAsMBE1haWwxFTATBgNVBAMMDCou 9 | ZG9tYWluLnRsZDEkMCIGCSqGSIb3DQEJARYVcG9zdG1hc3RlckBkb21haW4udGxk 10 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEIgZbJCpqmuuuXW1ls6Qh8zG7K+60uUhS 11 | LYNu94B3A6fuEswdVKzvQ3Z4fZQmiEvgU2s/LT+DEqCh8lF2JQSQxp8yKgytVCaM 12 | lpNBmKPMk3S0E/SYl0+udzGOXplff9TXo1MwUTAdBgNVHQ4EFgQUqQkfd/5TCSUk 13 | 3RTZyxt8b5P/gs4wHwYDVR0jBBgwFoAUqQkfd/5TCSUk3RTZyxt8b5P/gs4wDwYD 14 | VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNoADBlAjEAoHJEapvDVqiivOTfTUs4 15 | mDy2WWRdM8oZsHj34xi/4oaOafXbiqhEbLcCsJ2MQv7iAjBBfVGhh9WI/sC/awUB 16 | DNLHRE2mqMIEaqGG/QnvlsnEhsQuzOitQuOjhfDAzWumsXk= 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /test/share/ssl/ecdsa/selfsigned/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDARInQriRaZVLjmtweJ 3 | mqryTD/EfIAud0Lq8ut9w1WE4FWd0s11PCHTdjw/opAcj3yhZANiAAQiBlskKmqa 4 | 665dbWWzpCHzMbsr7rS5SFItg273gHcDp+4SzB1UrO9Ddnh9lCaIS+BTaz8tP4MS 5 | oKHyUXYlBJDGnzIqDK1UJoyWk0GYo8yTdLQT9JiXT653MY5emV9/1Nc= 6 | -----END PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /test/share/ssl/rsa/selfsigned/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGDjCCA/agAwIBAgIJAKXwo1hsHSQHMA0GCSqGSIb3DQEBCwUAMIGbMQswCQYD 3 | VQQGEwJGUjEPMA0GA1UECAwGRnJhbmNlMQ4wDAYDVQQHDAVQYXJpczEfMB0GA1UE 4 | CgwWTWFpbHNlcnZlciBjZXJ0aWZpY2F0ZTENMAsGA1UECwwETWFpbDEVMBMGA1UE 5 | AwwMKi5kb21haW4udGxkMSQwIgYJKoZIhvcNAQkBFhVwb3N0bWFzdGVyQGRvbWFp 6 | bi50bGQwHhcNMTcwODI4MDkwNjE5WhcNMjcwOTAzMDkwNjE5WjCBmzELMAkGA1UE 7 | BhMCRlIxDzANBgNVBAgMBkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxHzAdBgNVBAoM 8 | Fk1haWxzZXJ2ZXIgY2VydGlmaWNhdGUxDTALBgNVBAsMBE1haWwxFTATBgNVBAMM 9 | DCouZG9tYWluLnRsZDEkMCIGCSqGSIb3DQEJARYVcG9zdG1hc3RlckBkb21haW4u 10 | dGxkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvpMm3bMV7CchoPQO 11 | Em7GWKqS+muSDkU7tIDcmXzgkoX6Wvk/d2PF8ukJ/DWFGcPTgUsh0JdsVXW+uWiF 12 | RSuf717OulqSsjB9RspkhW1S/vfHXA83FFNhQHWHq5CfYuiMM3hwS9PjUL3Ocjdx 13 | gK6xlnMt7qWWV2j0LFvUQ6JS3fb/bSIuwkRT6Q7/Tsiz4MFNeOZwCCUPI9tTbDs4 14 | F5ZJpewm0UFS2tSt51V5Ao13UVfZ1jroiw6mASawdyvqwegBKNRYMg+HELIE0gnL 15 | EqcS0RqeUSz+e6kU4xzakKUkxKxfNcVcKJXc8y8SyhZstitCSPLScfpR520eI29k 16 | QfWb8yLjKG4fvpv2QIxzTtzSX/iHzDCovH26WRqhJarvL8zpCNTZ2z0zr7oUAwVg 17 | +7ygBN4qTmxf6j+UITod/MdOwyh/oy8H3dhChtXpGfO17VhKM0t2IcPlSuTnNVhT 18 | 5zs0TKRxuPzF41BidlikcmiACfNfg5/3dVQKV4sRVwsqf4iAqRTlBstLvt/a4f0K 19 | kwIte+5T2WNuEcRKf+9nO27VHDb2vCb1eJoidDK4CY6CkZpHvVYsJe7p5DWLslGU 20 | KBx0ChB7RZ2K6PHCTGiftZSBlDA6wgjn/KZah92+HR47k/22l4Zy6u7JglO8luHk 21 | NXrVWGHXeKZp0ah3wxfEDJmq7IUCAwEAAaNTMFEwHQYDVR0OBBYEFHdTH94h7hzm 22 | eG5QiyNlbuY5B3x6MB8GA1UdIwQYMBaAFHdTH94h7hzmeG5QiyNlbuY5B3x6MA8G 23 | A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAKJPM/L/N0dMRdAiMCvd 24 | yO+yB9XaopLDVl9z5JHypk8Nv4TsQE0G6xqa3nhpsHWMGUtXbdK5j1Nv33BHTWcD 25 | uRROo8rGQjtb/k4EgHQqAiSEDi6y12CldC0TluWXn1U9yPZxtSv7H7jkD564HS8B 26 | 1kw6JbawGduM76q/X2n001/Bm1RYkyQLQugCMJLCrojM9G6VKgznILHJENnaAj/S 27 | nCuux98OjLvA0giniygFQmrGpz/yS/SeNWpXMO8MhuowGdkMZ61RPcWb7NsdHWTJ 28 | i0Of3DBECXF/CMDJOc+sP3SLXs6y0nUU0KMWsNvCJ8R2yH3rJGblmLxogpnFHZrC 29 | NhCNsO9yiWlpqpG1mMSSUyA+P5VYfA5Irx55SHGBKB1NQPHSgkMXfqn4eCJA1mlE 30 | S/xmLhopwQgE/bsJxwf/4bEgF9bbK10XG19eHYdSW7XT2dl4lvSXFdEhLJby8FKE 31 | +BrB/OOUw2nhzZ0z6m64wA8coKiC2U06SDZYtZNbDL5gZXOJjz78uHqsmr8kpzhB 32 | 8C/2AggPWylcrndGhCUG4bO4ea6Y7LiljUjvBg/AJtfEjwItkRxL1I3DADmn6jW1 33 | /uxRz4Nk7rI12tdD8wHQf3pogjs+NjbnUpQ4u21uIqYuwdsfFkfwxfT3yH8Zh9Yp 34 | u0CYSLf89aRzuDbCGa8bhd0u 35 | -----END CERTIFICATE----- 36 | -------------------------------------------------------------------------------- /test/share/ssl/rsa/selfsigned/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | OyFYo2kryQoCVG4XU5JMK9tLruBVrA== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /test/share/tests/auth/imap-auth-master-wrong.txt: -------------------------------------------------------------------------------- 1 | a1 LOGIN sarah.connor@domain.tld*john.connor@domain.tld testpasswd12 2 | a5 LOGOUT 3 | -------------------------------------------------------------------------------- /test/share/tests/auth/imap-auth-master.txt: -------------------------------------------------------------------------------- 1 | a1 LOGIN john.doe@domain.tld*john.connor@domain.tld testpasswd2 2 | a2 LIST "" "*" 3 | a3 EXAMINE INBOX 4 | a5 LOGOUT 5 | -------------------------------------------------------------------------------- /test/share/tests/auth/imap-auth-wrong.txt: -------------------------------------------------------------------------------- 1 | a1 LOGIN sarah.connor@domain.tld badpassword 2 | a5 LOGOUT 3 | -------------------------------------------------------------------------------- /test/share/tests/auth/imap-auth.txt: -------------------------------------------------------------------------------- 1 | a1 LOGIN sarah.connor@domain.tld testpasswd12 2 | a2 LIST "" "*" 3 | a3 EXAMINE INBOX 4 | a5 LOGOUT 5 | -------------------------------------------------------------------------------- /test/share/tests/auth/pop3-auth-wrong.txt: -------------------------------------------------------------------------------- 1 | USER sarah.connor@domain.tld 2 | PASS badpassword 3 | QUIT 4 | -------------------------------------------------------------------------------- /test/share/tests/auth/pop3-auth.txt: -------------------------------------------------------------------------------- 1 | USER sarah.connor@domain.tld 2 | PASS testpasswd12 3 | LIST 4 | QUIT 5 | -------------------------------------------------------------------------------- /test/share/tests/auth/smtp-auth-login-wrong.txt: -------------------------------------------------------------------------------- 1 | EHLO mailserver 2 | AUTH LOGIN c2FyYWguY29ubm9yQGRvbWFpbi50bGQ= 3 | YmFkcGFzc3dvcmQ= 4 | QUIT 5 | -------------------------------------------------------------------------------- /test/share/tests/auth/smtp-auth-login.txt: -------------------------------------------------------------------------------- 1 | EHLO mailserver 2 | AUTH LOGIN c2FyYWguY29ubm9yQGRvbWFpbi50bGQ= 3 | dGVzdHBhc3N3ZDEy 4 | QUIT 5 | -------------------------------------------------------------------------------- /test/share/tests/auth/smtp-auth-plain-wrong.txt: -------------------------------------------------------------------------------- 1 | EHLO mailserver 2 | AUTH PLAIN AHNhcmFoLmNvbm5vckBkb21haW4udGxkAGJhZHBhc3N3b3Jk 3 | QUIT 4 | -------------------------------------------------------------------------------- /test/share/tests/auth/smtp-auth-plain.txt: -------------------------------------------------------------------------------- 1 | EHLO mailserver 2 | AUTH PLAIN AHNhcmFoLmNvbm5vckBkb21haW4udGxkAHRlc3RwYXNzd2QxMg== 3 | QUIT 4 | -------------------------------------------------------------------------------- /test/share/tests/clamav/test1.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | Delivered-To: john.doe@domain.tld 3 | Received: from mail.domain.tld 4 | by mail.domain.tld (Dovecot) with LMTP id edNuCu2rTltXKAAAIyBrWA 5 | for ; Wed, 18 Jul 2018 02:54:37 +0000 6 | Received: from mail.domain.tld (mail.domain.tld [IPv6:::1]) 7 | (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) 8 | (No client certificate requested) 9 | by mail.domain.tld (Postfix) with ESMTPS id 59C511680F 10 | for ; Wed, 18 Jul 2018 02:54:36 +0000 (UTC) 11 | Received: by mail.domain.tld with SMTP id p6-v6so2725008ljc.5 12 | for ; Tue, 17 Jul 2018 19:54:36 -0700 (PDT) 13 | MIME-Version: 1.0 14 | From: Sarah Connor 15 | Date: Tue, 17 Jul 2018 23:54:22 -0300 16 | Message-ID: 17 | Subject: test1 18 | To: john.doe@domain.tld 19 | Content-Type: multipart/mixed; boundary="000000000000c01a6a05713d2ecf" 20 | 21 | --000000000000c01a6a05713d2ecf 22 | Content-Type: multipart/alternative; boundary="000000000000c01a6805713d2ecd" 23 | 24 | --000000000000c01a6805713d2ecd 25 | Content-Type: text/plain; charset="UTF-8" 26 | 27 | test1 28 | 29 | --000000000000c01a6805713d2ecd 30 | Content-Type: text/html; charset="UTF-8" 31 | 32 |
test1
33 | 34 | --000000000000c01a6805713d2ecd-- 35 | --000000000000c01a6a05713d2ecf 36 | Content-Type: text/plain; charset="US-ASCII"; name="test1.txt" 37 | Content-Disposition: attachment; filename="test1.txt" 38 | Content-Transfer-Encoding: base64 39 | Content-ID: 40 | X-Attachment-Id: f_jjqj9iai0 41 | 42 | PCFET0NUWVBFIGh0bWw+DQo8aHRtbD4NCjxoZWFkPg0KPHRpdGxlPlNhbmVzZWN1cml0eTwvdGl0 43 | bGU+DQo8L2hlYWQ+DQoNCjxib2R5Pg0KPHA+Ym9keV9ycmc2M3VoajJ1Y3llY2NydXg3ZDgzYTRx 44 | ZDV1YTV2bmxnd2pwNmI2Zm1wenBvYnpqYWJmdGVodWhyYXhmYnl6enp6ejwvcD4NCjwvYm9keT4N 45 | Cg0KPC9odG1sPg0K 46 | --000000000000c01a6a05713d2ecf-- 47 | -------------------------------------------------------------------------------- /test/share/tests/clamav/test2.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | Delivered-To: john.doe@domain.tld 3 | Received: from mail.domain.tld 4 | by mail.domain.tld (Dovecot) with LMTP id yaajLgSLTlvAIQAAIyBrWA 5 | for ; Wed, 18 Jul 2018 00:34:12 +0000 6 | Received: from mail.domain.tld (mail.domain.tld [IPv6:::1]) 7 | (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) 8 | (No client certificate requested) 9 | by mail.domain.tld (Postfix) with ESMTPS id 49323167C2 10 | for ; Wed, 18 Jul 2018 00:34:12 +0000 (UTC) 11 | Received: by mail.domain.tld with SMTP id f8-v6so2529119ljk.1 12 | for ; Tue, 17 Jul 2018 17:34:12 -0700 (PDT) 13 | MIME-Version: 1.0 14 | From: Sarah Connor 15 | Date: Tue, 17 Jul 2018 21:34:01 -0300 16 | Message-ID: 17 | Subject: rrg63Uhj2UCyECcruX7D83A4qd5UA5vnlgwJp6b6fmPZpObZJAbftehuhRAXFby 18 | To: john.doe@domain.tld 19 | Content-Type: multipart/alternative; boundary="000000000000a57a8e05713b38d1" 20 | 21 | --000000000000a57a8e05713b38d1 22 | Content-Type: text/plain; charset="UTF-8" 23 | 24 | test2 25 | 26 | --000000000000a57a8e05713b38d1 27 | Content-Type: text/html; charset="UTF-8" 28 | 29 |
test2
30 | 31 | --000000000000a57a8e05713b38d1-- 32 | -------------------------------------------------------------------------------- /test/share/tests/clamav/test3.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | Delivered-To: john.doe@domain.tld 3 | Received: from mail.domain.tld 4 | by mail.domain.tld (Dovecot) with LMTP id qQ7+EyaLTlvAIQAAIyBrWA 5 | for ; Wed, 18 Jul 2018 00:34:46 +0000 6 | Received: from mail.domain.tld (mail.domain.tld [IPv6:::1) 7 | (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) 8 | (No client certificate requested) 9 | by mail.domain.tld (Postfix) with ESMTPS id D9D7313E14 10 | for ; Wed, 18 Jul 2018 00:34:45 +0000 (UTC) 11 | Received: by mail.domain.tld with SMTP id u14-v6so2163659lfu.0 12 | for ; Tue, 17 Jul 2018 17:34:45 -0700 (PDT) 13 | MIME-Version: 1.0 14 | From: Sarah Connor 15 | Date: Tue, 17 Jul 2018 21:34:35 -0300 16 | Message-ID: 17 | Subject: test3 18 | To: john.doe@domain.tld 19 | Content-Type: text/plain; charset="UTF-8" 20 | 21 | body_rrg63Uhj2UCyECcruX7D83A4qd5UA5vnlgwJp6b6fmPZ0ajdjkwjnSSDfsdfsdfnwerd 22 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-spam-to-existing-user.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: spam@gmail.com 3 | RCPT TO: john.doe@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Existing Local User 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | This is a test mail. 10 | 11 | XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X 12 | 13 | . 14 | QUIT 15 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-to-existing-alias-forward.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: user@gmail.com 3 | RCPT TO: postmaster@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Existing Local Alias 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 2 9 | Test:external-to-existing-alias2 10 | 11 | . 12 | QUIT 13 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-to-existing-alias-group.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: user@gmail.com 3 | RCPT TO: group@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Existing Local Alias 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | Test:external-to-existing-alias-group 10 | 11 | . 12 | QUIT 13 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-to-existing-alias.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: user@gmail.com 3 | RCPT TO: hostmaster@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Existing Local Alias 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | Test:external-to-existing-alias 10 | 11 | . 12 | QUIT 13 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-to-existing-system-account.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: user@gmail.com 3 | RCPT TO: root 4 | DATA 5 | From: Docker Mail Server 6 | To: Local Account 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | Test:external-to-existing-system-account 10 | 11 | . 12 | QUIT 13 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-to-existing-user-spam-learning.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: user@gmail.com 3 | RCPT TO: sarah.connor@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Existing Local User 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vehicula at magna ac molestie. 10 | Nam venenatis tincidunt magna, id tincidunt sem dictum quis. Vestibulum diam neque, venenatis in 11 | neque quis, lacinia mattis eros. Donec ornare eget mi ac interdum. Donec eu molestie velit. Duis 12 | sit amet tempus orci. Integer aliquet porttitor dui, quis lobortis eros sodales imperdiet. Cras 13 | consectetur odio velit, et scelerisque nunc tempor in. Lorem ipsum dolor sit amet, consectetur 14 | adipiscing elit. 15 | 16 | In elementum iaculis odio, nec viverra ex sodales sit amet. Fusce elementum sollicitudin erat, 17 | eget fringilla nibh dictum ut. Ut lobortis mauris et diam cursus molestie. Morbi arcu libero, 18 | auctor sed enim ac, tristique iaculis turpis. Phasellus a aliquet orci. Phasellus hendrerit 19 | eros ut placerat malesuada. Phasellus scelerisque felis risus, id porttitor lorem scelerisque 20 | et. Phasellus consequat risus vel tortor tempor fringilla. Nam vestibulum, elit et porta viverra, 21 | urna nulla sodales odio, vel vehicula leo felis in nibh. Nullam lobortis dui eu massa sagittis 22 | feugiat. Fusce sed massa nibh. Integer eget blandit massa. Nulla aliquet malesuada risus ut dapibus. 23 | Phasellus sit amet cursus metus. 24 | 25 | . 26 | QUIT 27 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-to-existing-user.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: user@gmail.com 3 | RCPT TO: john.doe@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Existing Local User 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | Test:external-to-existing-user 10 | 11 | . 12 | QUIT 13 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-to-non-existing-user.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: user@gmail.com 3 | RCPT TO: ghost@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Non Existing Local User 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | Test:external-to-non-existing-user 10 | 11 | . 12 | QUIT 13 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-to-valid-user-subaddress-with-default-separator.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: user@gmail.com 3 | RCPT TO: john.doe+subname@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Existing Local User 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | Test:external-to-valid-user-subaddress-with-default-separator 10 | 11 | . 12 | QUIT 13 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-to-valid-user-subaddress.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: user@gmail.com 3 | RCPT TO: john.doe:subname@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Existing Local User 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | Test:external-to-valid-user-subaddress 10 | 11 | . 12 | QUIT 13 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/external-virus-to-existing-user.txt: -------------------------------------------------------------------------------- 1 | HELO mx.gmail.com 2 | MAIL FROM: virus@gmail.com 3 | RCPT TO: john.doe@domain.tld 4 | DATA 5 | From: Docker Mail Server 6 | To: Existing Local User 7 | Date: Sat, 28 Nov 2016 12:00:00 +0200 8 | Subject: Test Message 9 | 10 | Content-type: multipart/mixed; boundary="emailboundary" 11 | MIME-version: 1.0 12 | 13 | This is a multi-part message in MIME format. 14 | --emailboundary 15 | Content-type: text/plain 16 | 17 | This is the body of the message. 18 | 19 | --emailboundary 20 | Content-type: application/octet-stream 21 | Content-transfer-encoding: base64 22 | 23 | UEsDBAoAAAAAAOCYuCg8z1FoRAAAAEQAAAAJAAAAZWljYXIuY29tWDVPIVAlQEFQ 24 | WzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNU 25 | LUZJTEUhJEgrSCpQSwECFAAKAAAAAADgmLgoPM9RaEQAAABEAAAACQAAAAAAAAAB 26 | ACAA/4EAAAAAZWljYXIuY29tUEsFBgAAAAABAAEANwAAAGsAAAAAAA== 27 | 28 | 29 | --emailboundary-- 30 | 31 | . 32 | QUIT 33 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/internal-rejected-user-to-existing-user.txt: -------------------------------------------------------------------------------- 1 | HELO mailserver 2 | AUTH PLAIN AGpvaG4uZG9lQGRvbWFpbi50bGQAdGVzdHBhc3N3ZDEy 3 | MAIL FROM: john.doe@domain.tld 4 | RCPT TO: sarah.connor@domain.tld 5 | DATA 6 | From: Docker Mail Server 7 | To: Existing Local User 8 | Date: Sat, 28 Nov 2016 12:00:00 +0200 9 | Subject: Test Message 10 | Test:internal-rejected-user-to-existing-user 11 | 12 | . 13 | QUIT 14 | -------------------------------------------------------------------------------- /test/share/tests/email-templates/internal-user-to-existing-user.txt: -------------------------------------------------------------------------------- 1 | HELO mailserver 2 | AUTH PLAIN AHNhcmFoLmNvbm5vckBkb21haW4udGxkAHRlc3RwYXNzd2QxMg== 3 | MAIL FROM: sarah.connor@domain.tld 4 | RCPT TO: john.doe@domain.tld 5 | DATA 6 | From: Docker Mail Server 7 | To: Existing Local User 8 | Date: Sat, 28 Nov 2016 12:00:00 +0200 9 | Subject: Test Message 10 | Test:internal-user-to-existing-user 11 | 12 | . 13 | QUIT 14 | -------------------------------------------------------------------------------- /test/share/tests/sieve/trigger-spam-ham-learning.txt: -------------------------------------------------------------------------------- 1 | a1 LOGIN sarah.connor@domain.tld testpasswd12 2 | a2 SELECT INBOX 3 | a3 COPY 1 Spam 4 | a4 STORE 1 +flags (\Deleted) 5 | a5 EXPUNGE 6 | a6 SELECT Spam 7 | a7 COPY 1 INBOX 8 | a8 LOGOUT 9 | -------------------------------------------------------------------------------- /test/share/traefik/acme.v1.json: -------------------------------------------------------------------------------- 1 | { 2 | "PrivateKey": "MIIJJwIBAAKCAgEAy5a71suIqvEtovDmDVQ3SSNagk5IVCFI/TvqWpEXSrdbcDE2C+PTEtEUJuLkYwygcpiWYbPmXgdS628vQCw5Uo4DeDyHiuysJOWBLaWow3p9goOdhnPbGBq0liIR9xXyRoctdipVk8UiO9scWsu4jMBM3sMr7/yBWPfYYiLEQmZGFO3iE7Oqr55h/kncHIj5lUQY1j/jkftqxlxUB5/0quyJ7l915j5QY++eY7h4GEhRvx0TlUpi+CnRtRblGeDDDilXZD6bQN2962WdKecsmRaYx+ttLz6jCPXz2VDJRWNcIS501ne2Zh3hzw/DS6IRd2GIia1Wg4sisi9epC9sumXPHi6xzR6+/i/nsFjdtTkUcV8HmorOYoc820KQVZaLScxa8e7+ixpOd6mr6AIbEf7dBAkb9f/iK3GwpqKD8yNcaj1EQgNSyJSjnKSulXI/GwkGnuXe00Qpb1a8ha5Z8yWg7XmZZnJyAZrmK60RfwRNQ1rO5ioerNUBJ2KYTYNzVjBdob9Ug6Cjh4bEKNNjqcbjQ50/Z97Vw40xzpDQ/fYllc6n92eSuv6olxFJTmK7EhHuanDzITngaqei3zL9RwQ7P+1jfEZ03qmGrQYYqXcsS46PQ8cE+frzY2mKp16pRNCG7+03gKVGV0JHyW1aYbevNUk7OumCAXhC2YOigBkCAwEAAQKCAgA8XW1EuwTC6tAFSDhuK1JZNUpY6K05hMUHkQRj5jFpzgQmt/C2hc7H/YZkIVJmrA/G6sdsINNlffZwKH9yH6q/d6w/snLeFl7UcdhjmIL5sxAT6sKCY0fLVd/FxERfZvp3Pw2Tw+mr7v+/j7BQm6cU1M/2HRiiB9SydIqMTpKyvXB6NC6ceOFbQTL9GxlQvKyEPbS/kiH/3vRB7I5d1GfPZmNfcp6ark9X0my8VK4HRSo36H8t/OhrfLrZXvh/O82aHVf0OTv/d8AgU/jNu+XVXoXegUfWglQFDChJf1KuaE+g5w1tqgFDNgkGRD475soXA6xgZi0Iw/B9tN3zALzT4IiAW1q72feeTgKOMA2zGtKXxQZZSOV+DuWFZNz/tT7XqGQThqxM09CHv2WGOe80vobtegXYTUt90hysrqIZmBW5XYdzQlJh1KWTtfCaTrWd47kbGvhkEPc8aA3Ji4/AqfkVXiqwaLu+MSlgzPpRj7U7UAIDqnpZjgttgLp74Ujnk3bTaUzdyyNqYDBG3IFGr/Sv+2GQDAUn/PYRJKWr0BteqOzX9zvW3zY8g9CYVXfK/AW3RMWLV8ly6vH/gWqa9gEuzRNRlzjUU6/HCVbUx3UT8RMWH2TQ0uuQZr5JX1iTwjeeT0dEIly1NnRQC92wcrE4UUTBEF3krGVpDBf0AQKCAQEA4jB8w+2fwzbF8X+gCODcY7sTeJRunzGy+jbdaLkcThuylga+6W3ZgWx0BD30ql9K2mouCVu86fCTnBeXXEC3QoTdgw/EzJ83+4JU3QSDdzs9Ta9vLHyvrpUkQfZ8UZpeLLmFsmsBMbBbnfw0S1TzXDsgrAc+G4tia8nO/Iqu75kEMGzmHQAvmN3iSqc1aTS4qumbB19g+v+csq9NEht4F9jt39KotG+OD3MxCxtMu7vxAkJRjFFcgcbb2Rtqe/kQEKA1vLEAJg27lV4k8XibCSerVUR6IzT8WZHrNiXmpRguTLl2k8uFUdCOOx6aLGyRVJ6+8SgIsMR540vnxwQzEQKCAQEA5mu2wtWT19mvXopC3easPsXIPzc5oaRkqfWZYT1KHcVQ7NIXsE3vCjcf/3igZ8l/FVQ4G4fpk/GoTqlpV5Aq/JHCpVOR2O69uB+W4kWgliejpHvF9gszzAYnC8lIXqDbWiinBhmm3ii8sDGAoBaSDw5NMUq3mI+nd8zZ+jx1bLBczDafmQ0YKr8k0YaROxIgoBgDOQDdSqG387lwzpza2DKI5Al3HfS42zjT0RmBahPiuT2aEoUZmIYuvFY0fEjfkpbdvLyexHfZCILRUGlG1nAwASFg86lp+mFSBJ3E3cvbP0CpbFGxon5u4Ao3/7htoOh6huh7MQ91h41fv1hsiQKCAQAe7WRR4e7jYVzlbX7zV9Oqq0y5QwpxJ/mB7viNNiphn7Xmf5uhDU0dPjgK0HHgzdDNVpFe5DVLg4KbaDpg+dRU+xfSsNhG5kpgUGzMH67eIbJ7Kc64tX/MDkZ74nkTK1lPIjrer3TlV2jfjDmWR1JTPR51hzP9ziwx8tEjhM7woeqJuIoqUvkvHL+xV3WdIgFSFUkGVAtNpp/FauTN4gWktRupbAN3UH2LLUP6ccwnK0aD+Y9u8T0F3av33qDLvL1umIlgeI89pMkOXmYMwmHoeY0axpcwszECCkqwB7SmxEyoXv+Qq9ZZ3ntkKAYKpvmkKWSQUtoFWYgVBS727mMRAoIBABLdwusU/bPwuPEutObiWjwRiaHTbb6UbUGVQGe70vO5EjUxxorC9s2JUe9i+w9EakleyfFHIZLheHxoVp26yio/7QYIX6q5cYM/4uTH+qwQts9i6wSISkdsQYovguNsnEk3huVy+Dy8bSaoBvYUowTkkOF2Uq4FJRskBLz+ckbh8dcuqcaoUdA+Mk+NixqhE1bIYIssTPItZ5hnGJtyMGD/UkIJnF0ximk4r+8w/W2oDypHpvPZPg1E/1KgZE/Az7166NDpSL6haX3O6ECDPi+Uo/mTuBJ7TpgXm9WQ7WuTo3H8Y2LhFYBOhdmGPKuNeDxyjIW7R0rvDxp4MtzB6rECggEAJIl7/qp1lxUQPQJRTsEYBkOtdRw0IGG1Rcj0emhHaBN05c9opCy+Osb7mVeU5ZiULe5kD02phL+36pEumprz7QzN46Y5pZc8AQ2W/QkeL4Wo9U9QzczvQQzc1EqrBkzvQTZtBhn4DRzz0IuTn1beVyHtBZeNpBFgMQFv9VYQuUNwFoTOkkQrBRnYbXH6KEnhF3c/1Hzi4KHVdHdfZ3LH7KFQJ34xio0q2tWQSQYeybmwOXdd9sxpz/Y4KBS9fqm7UrwnPK8yuOc05HLEaws+1iam5YyJprlQo3mGKe0wRztwn44HDeQr70LlFm0lzigVAv0hSiWO1Q5hJL7nDu8m/Q==", 3 | "DomainsCertificate": { 4 | "Certs": [ 5 | { 6 | "Certificate": { 7 | "Domain": "mail.domain.tld", 8 | "PrivateKey": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS1FJQkFBS0NBZ0VBdVNoTTR4enF6cE5YcFNaNnAvZnQrRmt5VmgyK1BSZXJUelV0OERRSng2UkVjQS9FCnN2RnNIVmNOSkZMS2twYTNlOEd3SUZBakJQNnJPK3hoR1JjWlJrdENON1gyOW5LZFhGbHZkYzJxd0hyTFF5WWkKTTB3ODhTck41VERiNi96TWU2dTB0dERiYWtDbDd6ZEJKUXJ6a1h5ZU1MeVkzTUs3aVkrMHpwL2JqMVhvbk5DdQpaQStkZ3hsMVNrV01DVUYvQk9HNWFyT1hwb0x4S0dQWGdzV3hOTVNLVmJKSHczL3ZqNTViZU92Um5lT3BNWlhvCmMwOWpZT3VBakNka1Z5czBSWHJLNWNCRDRMbVRXdnN4MFdTK2VMVHlGTTdQTHVZM3lEWkNNWEhjVmlqRHhnbFMKYjB1ZVRQcGFUWEQwYkxqZ0RNOUVEdE15ZEJzMUNPWlpPWG9ickN5Q2I1eWxTOFdVd1NzVXM1UldxZnlVbnAvcgpSNGx2c2RZOWRVZjRPdkNMVnJvWWk5NWFGc1Zxa0xLOExuL0Eyc3kxYWlDTnR4RmpKOXRXbWU0V0NhdzRoU0YvCkR4NWVNNWNYR2JSYXduVlZJQlZXeHhzNTBPMFJlUWRvbXBQZEFNS1RDWk9SRmxYaDdOWTdxQVdWRGtpdzhyam8Kekd3Ni9XdjlOR3hTNTliKzc0YVAxcjBxOTZ2RS9Rdi8zTCtjbjhiN0lBLytPYmFKdzhIT3RGbXc4RjBxQkN3MAprYWVVSloxb1JueGFYQUo4RHhHREpFOVdNUzh0QmJtVm16YkxoRkMzeDdVc0xGeTBrSzh1SFBFT3dQb2NKNUFUCkE1UHBvclNEMmFleHA0Z3VqYVp5c1JManpmY0dnaTdva0JFNlZVNWVqRE1iYS9lNERQNEJQUVg5VmtVQ0F3RUEKQVFLQ0FnQmZjMWdYcUp1ZmZMT3REcVlpbXh4UmIrSVVKT2NpWldaSndmZDVvY244NGtEcHFDZFZ2RUZvNnF4NgpzamQ5MURhb2xOUHdCSC9aSGxRMTR3aTNQNEluQzdzS0wwTXVEeTN5SXFUa0RPOWVwSzdPWWdVMWZyTFgvS0lCCjZlc2x2Ny9HYldFTzhhSjdKdktqM0U4NEFtcEg4UDgzenJIYTlJUnJTT3NEcmNNcEpEZHpSOXp1OW1IVDZMYmYKWC9UdC9KYTNkSW42YUxUZ0FSYkRKSjAvN0J3TFFOcXpqT0dUOWdzUWRhbGdMK2x5eEo4L1ViRndhRmVwNmgzdApvbzBHcHQ0ZWgwdTdueDhlNVd3Q2RnWmJsTnpnS3grMC9Gd3dLRHhQZVRFc2ZpOEJONmlkR2NjbVdzd3prTWdtCnJmbERaeGNSWTNRSlZIVHBCL0dTTWZXRFBPQ3dRdGltQk1WN3kxM2hPMTdPWXpSNDBMZnpUalJBbmtna2V2eWYKcFowb3dLR3o4QS9haHhRWWJmYVQ5VEhXV0wrYUpYeUhFanBKckp5aTg3UExVbzhsOFVydU56MDRWNXpLOFJPbgo2cG9EWmVtbm1EYWRlU09pK3hZRWlGT1NwSXNWbzlpcm9jUGFKN2YzYWpiNUU4RHpuN1o1MmhzL2R6akpLcFZJCm5mVDFkUU9SZEowSXRUNlRlQ2RTL0dpS25IS1RtNjR2T21IbmlJcm8rUGRhUmFjV0IrTUJ0VytRd0cyUStyRGkKc3g4NlpQbHRpTVpLMDZ5TVlyVHZUdGk2aFVGaUY5cWh4b3RGazdNQkNrZlIwYUVhaUREQUpKNm1jb1lpRUQ2QgpBVGJhVmpVaGNaUiswYkRST25PN0ozRk5rZmx3K2dMaVhvcXFRRW9pU2ZWb2h5SWY3UUtDQVFFQThjYTM5K0g4CjN3L2Qrcm0yUGNhM0RMQnBYaWU4Z3ZYcGpjazVYSkpvSGVmbnJjZWQrcFpXaTZEYncwYld0MEdtYkxmVjJNSlAKV2I1aTZzSXhmdkN3YlFqbHY0UnExMVA5ZEswT3poMnVpKzZ6cXVBMG5YTVcrN0lJS0cvdDhmS2NJZGRRNnRGcwpFclFVTFBDak56ODA2cHBiSlhPRmVvMW1BK293TGhHNlA3dDhCdlZHSk1NaTNxejNlSUNuVVE2eDNFY01ITXNuClhrM21DUzI1WUZaNk96cytFK254cGVraTAzZmQwblp3UE1jdElHZys1c3hleE9zREsrTHlvb2FqQnc5N0oyUzIKcUNNWXFtT0tLcmxEQ3Y1WmQ4dlZLN3hXVmpKRVhGTTNMZ2pieHBRcCtuVXNVVWxwS01LOVlGS0lRREl0RU9aMApWcWExTXJaOElzN1l5d0tDQVFFQXhBemZIa2pIVGlvTHdZbG5EcEk0MWlOTDh5Y0ZBallrTC94dWhPU2tlVkE4CjdRWDZPZUpDekR3Z0FUYXVqOWR6Y0wwby9yTndWV0xWcnQ3OXk3YnJvVDdFREZKWVNTY25GRXNMTlVWSXRncGkKckNSUXJTL1F2TkVGTmE5K0pRc1dmYkdBNHdIUTFaSjI4MFp1cWMvNlEyUi9kZVh3cUZBQVBHN2NIcEhHWlR6ZQoyRmFRUHFLRkV4WlEyZkpvRys0SVBRNHVQVERybXlGMmVUWXk2T3BaaDBHbWJRYlVTa1dFWDlQRmF1cHJIWVdGCk8wK25DaVVPNVRaMFZoaGR2dUNKMWdPclZHYzhBUlJtUVZ1aUNEWTZCaGlvVTU0ZmZsSXlDTXZ5a3MwcmRXZ3MKWVJ2TmN4TXNlRGJpTDRKSURkMHhiN1d4VUdmVjRVNHZPMks5Vms1N0x3S0NBUUVBMkd1eE1jcXd1RnRUc0tPYwpaaUFDcXZFZTRKRmhSVGtySHlnSW1MelZSaS9ZU3M1c3MycnZmWDA0T3N5bVZ0UUZUVHdoeUMzbktjWXFkVW52ClZGblBFMHJyblV2Qzk0elBUQ205SHZPaTBzK1JORndOdlFMUWgrME5NR1ZBOFZyaU44aXRQZ1RJWU5XaFdianQKNFA1TE45V0QwVHBmT1J4cFBRZmNxT0JsZjdjcmhtNzNvdUNwemZtMmE3OStCaWpKUFF5NzR1cFhDeXRmeHNlUApNSlU0Uk56NjdJaDFMclpKM2xGbDFvYitZT2xKazhDOHpZd1RLT0hWck9zeGxobyt4SXN2Q2t3MDFMelZ6Mi9hCnRmT3Y5NTlHSnQzbXE0ZWpJUFZPQy9iUlpmdTMvMEdSY2dpQTZ5SnpaM0VxWTVaOU1EbTU3VzdjcE5RRlRxZmEKNXEyUmtRS0NBUUErNGhZSzQ3TXg2aUNkTWxKaEJSdS82OUJucktOWm96NFdPalRFNFlXejk3MmpGU0Mrd2tsRQpzeUJjNDBvNGp4WFRHb2wwc04rZU03WndnY3dNTko3OXVHRXZ4cFhVMlA4YTdqc3BHaEVKZXVsTlo5U015R0orCnZkaWE4TEJZZDJiK2FCbjhOay9pd1Rqd0xTNC92NXI1Vk5uaFdpRElDK2tYZVVPWGRwQ1pWbDN3TEV2V0cxRHQKMzJHTmxzZzM5VENsVE5BZUJudjc1VTdYOEQrQ0gvRVpoa0E0aGxFL2hXN0JRZTczclRzd1creHhLc3BjWWFpVwpjdEg3NzVMYUw3Rm1lUVRTYk01OVZpcTZXZ2J0OVY3Rko5R09DSkQzZHF2ZjBITDlEVndjSzQ3WWt3OWlFc3RYCnY5cnEvREhhYUpGNzBGNlFlTTNNbDhSa212WTZJYkEzQW9JQkFRRGt6RmZLeG9HQ3dWUDlua3k4NmFQSjFvd2kKc2FDZEx6RjRWTENRZzkrUXJITzEyY0p5MFFQUnJ2cUQyMGp1cDFlOWJhWVZzbkdYc1FZTFg2NVR6UzJSSCtlSAp6S0NPTTdnMVE3djMxNWpjMDMvN1lQck4rb3RrV0VBOUkyaDZjUE1vY3c0aERTNk02OFlxQVlKTS9RclVhenZhCnhBTFJaZEVkQW1xWDA4VHhuY1hRUEVxYkk0ZnlSZ2pVM1BYR3RRaFFFbERpR2kwbThjQTJNTXdsR1RmbTdOSXgKaENjZ2ZkL296TEp2VUhiMkxLRi82cXEySmJVRHlOMkVoK0xSZUJjdnp6Y1grZE5MdGQxY0Uvcm1SM2hMbWxmNgo3KzRpTVMxK0t1eWV3VlJVUEE1c1F1aUYyVUVoeEs1MUpZK1FpOG9HbERKdGRrOXB3QlZNN1F0WW9KVEwKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K", 9 | "Certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZvakNDQklxZ0F3SUJBZ0lUQVAvRTgvRk43NTdtN0dvRklyWEF1cU0zblRBTkJna3Foa2lHOXcwQkFRc0YKQURBZk1SMHdHd1lEVlFRRERCUm9NbkJ3ZVNCb01tTnJaWElnWm1GclpTQkRRVEFlRncweE9EQXhNVFV3TnpJNQpNREJhRncweE9EQTBNVFV3TnpJNU1EQmFNRVF4RXpBUkJnTlZCQU1UQ214dlkyRnNNUzVqYjIweExUQXJCZ05WCkJBVVRKR1ptWXpSbU0yWXhOR1JsWmpsbFpUWmxZelpoTURVeU1tSTFZekJpWVdFek16YzVaRENDQWlJd0RRWUoKS29aSWh2Y05BUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTGtvVE9NYzZzNlRWNlVtZXFmMzdmaFpNbFlkdmowWApxMDgxTGZBMENjZWtSSEFQeExMeGJCMVhEU1JTeXBLV3QzdkJzQ0JRSXdUK3F6dnNZUmtYR1VaTFFqZTE5dlp5Cm5WeFpiM1hOcXNCNnkwTW1Jak5NUFBFcXplVXcyK3Y4ekh1cnRMYlEyMnBBcGU4M1FTVUs4NUY4bmpDOG1OekMKdTRtUHRNNmYyNDlWNkp6UXJtUVBuWU1aZFVwRmpBbEJmd1RodVdxemw2YUM4U2hqMTRMRnNUVEVpbFd5UjhOLwo3NCtlVzNqcjBaM2pxVEdWNkhOUFkyRHJnSXduWkZjck5FVjZ5dVhBUStDNWsxcjdNZEZrdm5pMDhoVE96eTdtCk44ZzJRakZ4M0ZZb3c4WUpVbTlMbmt6NldrMXc5R3k0NEF6UFJBN1RNblFiTlFqbVdUbDZHNndzZ20rY3BVdkYKbE1FckZMT1VWcW44bEo2ZjYwZUpiN0hXUFhWSCtEcndpMWE2R0l2ZVdoYkZhcEN5dkM1L3dOck10V29namJjUgpZeWZiVnBudUZnbXNPSVVoZnc4ZVhqT1hGeG0wV3NKMVZTQVZWc2NiT2REdEVYa0hhSnFUM1FEQ2t3bVRrUlpWCjRleldPNmdGbFE1SXNQSzQ2TXhzT3Yxci9UUnNVdWZXL3UrR2o5YTlLdmVyeFAwTC85eS9uSi9HK3lBUC9qbTIKaWNQQnpyUlpzUEJkS2dRc05KR25sQ1dkYUVaOFdsd0NmQThSZ3lSUFZqRXZMUVc1bFpzMnk0UlF0OGUxTEN4Ywp0SkN2TGh6eERzRDZIQ2VRRXdPVDZhSzBnOW1uc2FlSUxvMm1jckVTNDgzM0JvSXU2SkFST2xWT1hvd3pHMnYzCnVBeitBVDBGL1ZaRkFnTUJBQUdqZ2dHd01JSUJyREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdIUVlEVlIwbEJCWXcKRkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQ01Bd0dBMVVkRXdFQi93UUNNQUF3SFFZRFZSME9CQllFRk5LZQpBVUZYc2Z2N2lML0lYVVBXdzY2ZU5jQnhNQjhHQTFVZEl3UVlNQmFBRlB0NFR4TDVZQldETEo4WGZ6UVpzeTQyCjZrR0pNR1lHQ0NzR0FRVUZCd0VCQkZvd1dEQWlCZ2dyQmdFRkJRY3dBWVlXYUhSMGNEb3ZMekV5Tnk0d0xqQXUKTVRvME1EQXlMekF5QmdnckJnRUZCUWN3QW9ZbWFIUjBjRG92THpFeU55NHdMakF1TVRvME1EQXdMMkZqYldVdgphWE56ZFdWeUxXTmxjblF3T1FZRFZSMFJCREl3TUlJS2JHOWpZV3d4TG1OdmJZSVFkR1Z6ZERFdWJHOWpZV3d4CkxtTnZiWUlRZEdWemRESXViRzlqWVd3eExtTnZiVEFuQmdOVkhSOEVJREFlTUJ5Z0dxQVloaFpvZEhSd09pOHYKWlhoaGJYQnNaUzVqYjIwdlkzSnNNR0VHQTFVZElBUmFNRmd3Q0FZR1o0RU1BUUlCTUV3R0F5b0RCREJGTUNJRwpDQ3NHQVFVRkJ3SUJGaFpvZEhSd09pOHZaWGhoYlhCc1pTNWpiMjB2WTNCek1COEdDQ3NHQVFVRkJ3SUNNQk1NCkVVUnZJRmRvWVhRZ1ZHaHZkU0JYYVd4ME1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ3A0Q2FxZlR4THNQTzQKS2JueDJZdEc4bTN3MC9keTVVR1VRNjZHbGxPVTk0L2I0MmNhbTRuNUZrTWlpZ01IaUx4c2JZVXh0cDZKQ3R5cQpLKzFNcDFWWEtSTTVKbFBTNWRIaWhxdHk1U3BrTUhjampwQSs3U2YyVWtoNmpKRWYxTUVJY2JnWnpJRk5IT0hYClVUUUppVFhKcno3blJDZnlQWFZtbWErUGtIRlU4R0VEVzJGOVptU1kzVFBiQWhiWkV2UkZubjUrR1lxbkZuancKWWw3Y0I2MXYwRzVpOGQwbnVvbTB4a2hiNTU3Y3BiZHhLblhsaFU4N2RZSTR5SUdPdUFGUWpYcXFXN2NIZCtXUQpWSDB2dFA3cEgrRmt2YnY4WkkxMHMrNU5ZcCtzZjFQZGQxekJsRmdNSGF3dnFFYUg3SU9sejdkajlCdmtVc0dpClhxQWVqQnFPCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVpakNDQTNLZ0F3SUJBZ0lDRWswd0RRWUpLb1pJaHZjTkFRRUxCUUF3S3pFcE1DY0dBMVVFQXd3Z1kyRmoKYTJ4cGJtY2dZM0o1Y0hSdlozSmhjR2hsY2lCbVlXdGxJRkpQVDFRd0hoY05NVFV4TURJeE1qQXhNVFV5V2hjTgpNakF4TURFNU1qQXhNVFV5V2pBZk1SMHdHd1lEVlFRREV4Um9ZWEJ3ZVNCb1lXTnJaWElnWm1GclpTQkRRVENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUlLUjNtYUJjVVNzbmNYWXpRVDEzRDUKTnIrWjNtTHhNTWgzVFVkdDZzQUNtcWJKMGJ0UmxnWGZNdE5MTTJPVTFJNmEzSnUrdElaU2RuMnYyMUpCd3Z4VQp6cFpRNHp5MmNpbUlpTVFEWkNRSEp3ekM5R1puOEhhVzA5MWl6OUgwR28zQTdXRFh3WU5tc2RMTlJpMDBvMTRVCmpvYVZxYVBzWXJaV3ZSS2FJUnFhVTBoSG1TMEFXd1FTdk4vOTNpTUlYdXlpd3l3bWt3S2JXbm54Q1EvZ3NjdEsKRlV0Y05yd0V4OVdnajZLbGh3RFR5STFRV1NCYnhWWU55VWdQRnpLeHJTbXdNTzB5TmZmN2hvK1FUOXg1K1kvNwpYRTU5UzRNYzRaWHhjWEtldy9nU2xOOVU1bXZUK0QyQmhEdGtDdXBkZnNaTkNRV3AyN0ErYi9EbXJGSTlOcXNDCkF3RUFBYU9DQWNJd2dnRytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3UXdZRFZSMGVCRHd3T3FFNE1BYUMKQkM1dGFXd3dDb2NJQUFBQUFBQUFBQUF3SW9jZ0FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpBQUFBQUFBd0RnWURWUjBQQVFIL0JBUURBZ0dHTUg4R0NDc0dBUVVGQndFQkJITXdjVEF5QmdnckJnRUZCUWN3CkFZWW1hSFIwY0RvdkwybHpjbWN1ZEhKMWMzUnBaQzV2WTNOd0xtbGtaVzUwY25WemRDNWpiMjB3T3dZSUt3WUIKQlFVSE1BS0dMMmgwZEhBNkx5OWhjSEJ6TG1sa1pXNTBjblZ6ZEM1amIyMHZjbTl2ZEhNdlpITjBjbTl2ZEdOaAplRE11Y0Rkak1COEdBMVVkSXdRWU1CYUFGT21rUCs2ZXBlYnkxZGQ1WUR5VHBpNGtqcGVxTUZRR0ExVWRJQVJOCk1Fc3dDQVlHWjRFTUFRSUJNRDhHQ3lzR0FRUUJndDhUQVFFQk1EQXdMZ1lJS3dZQkJRVUhBZ0VXSW1oMGRIQTYKTHk5amNITXVjbTl2ZEMxNE1TNXNaWFJ6Wlc1amNubHdkQzV2Y21jd1BBWURWUjBmQkRVd016QXhvQytnTFlZcgphSFIwY0RvdkwyTnliQzVwWkdWdWRISjFjM1F1WTI5dEwwUlRWRkpQVDFSRFFWZ3pRMUpNTG1OeWJEQWRCZ05WCkhRNEVGZ1FVKzNoUEV2bGdGWU1zbnhkL05CbXpMamJxUVlrd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFBMFkKQWVMWE9rbHg0aGhDaWtVVWwrQmRuRmZuMWcwVzVBaVFMVk5JT0w2UG5xWHUwd2puaE55aHFkd25maFlNbm95NAppZFJoNGxCNnB6OEdmOXBubExkL0RuV1NWM2dTKy9JL21BbDFkQ2tLYnk2SDJWNzkwZTZJSG1JSzJLWW0zam0rClUrK0ZJZEdwQmRzUVRTZG1pWC9yQXl1eE1ETTBhZE1rTkJ3VGZRbVpRQ3o2bkdIdzFRY1NQWk12WnBzQzhTa3YKZWt6eHNqRjFvdE9yTVVQTlBRdnRUV3JWeDhHbFIycWZ4LzR4YlFhMXYyZnJOdkZCQ21PNTlnb3oram5XdmZUdApqMk5qd0RaN3ZsTUJzUG0xNmRiS1lDODQwdXZSb1pqeHFzZGMzQ2hDWmpxaW1GcWxORy94b1BBOCtkVGljWnpDClhFOWlqUEljdlc2eTFhYTNiR3c9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" 10 | } 11 | } 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/share/traefik/acme.v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "Account": { 3 | "PrivateKey": "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" 4 | }, 5 | "Certificates": [ 6 | { 7 | "Domain": { 8 | "Main": "*.domain.tld" 9 | }, 10 | "Certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZvakNDQklxZ0F3SUJBZ0lUQVAvRTgvRk43NTdtN0dvRklyWEF1cU0zblRBTkJna3Foa2lHOXcwQkFRc0YKQURBZk1SMHdHd1lEVlFRRERCUm9NbkJ3ZVNCb01tTnJaWElnWm1GclpTQkRRVEFlRncweE9EQXhNVFV3TnpJNQpNREJhRncweE9EQTBNVFV3TnpJNU1EQmFNRVF4RXpBUkJnTlZCQU1UQ214dlkyRnNNUzVqYjIweExUQXJCZ05WCkJBVVRKR1ptWXpSbU0yWXhOR1JsWmpsbFpUWmxZelpoTURVeU1tSTFZekJpWVdFek16YzVaRENDQWlJd0RRWUoKS29aSWh2Y05BUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTGtvVE9NYzZzNlRWNlVtZXFmMzdmaFpNbFlkdmowWApxMDgxTGZBMENjZWtSSEFQeExMeGJCMVhEU1JTeXBLV3QzdkJzQ0JRSXdUK3F6dnNZUmtYR1VaTFFqZTE5dlp5Cm5WeFpiM1hOcXNCNnkwTW1Jak5NUFBFcXplVXcyK3Y4ekh1cnRMYlEyMnBBcGU4M1FTVUs4NUY4bmpDOG1OekMKdTRtUHRNNmYyNDlWNkp6UXJtUVBuWU1aZFVwRmpBbEJmd1RodVdxemw2YUM4U2hqMTRMRnNUVEVpbFd5UjhOLwo3NCtlVzNqcjBaM2pxVEdWNkhOUFkyRHJnSXduWkZjck5FVjZ5dVhBUStDNWsxcjdNZEZrdm5pMDhoVE96eTdtCk44ZzJRakZ4M0ZZb3c4WUpVbTlMbmt6NldrMXc5R3k0NEF6UFJBN1RNblFiTlFqbVdUbDZHNndzZ20rY3BVdkYKbE1FckZMT1VWcW44bEo2ZjYwZUpiN0hXUFhWSCtEcndpMWE2R0l2ZVdoYkZhcEN5dkM1L3dOck10V29namJjUgpZeWZiVnBudUZnbXNPSVVoZnc4ZVhqT1hGeG0wV3NKMVZTQVZWc2NiT2REdEVYa0hhSnFUM1FEQ2t3bVRrUlpWCjRleldPNmdGbFE1SXNQSzQ2TXhzT3Yxci9UUnNVdWZXL3UrR2o5YTlLdmVyeFAwTC85eS9uSi9HK3lBUC9qbTIKaWNQQnpyUlpzUEJkS2dRc05KR25sQ1dkYUVaOFdsd0NmQThSZ3lSUFZqRXZMUVc1bFpzMnk0UlF0OGUxTEN4Ywp0SkN2TGh6eERzRDZIQ2VRRXdPVDZhSzBnOW1uc2FlSUxvMm1jckVTNDgzM0JvSXU2SkFST2xWT1hvd3pHMnYzCnVBeitBVDBGL1ZaRkFnTUJBQUdqZ2dHd01JSUJyREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdIUVlEVlIwbEJCWXcKRkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQ01Bd0dBMVVkRXdFQi93UUNNQUF3SFFZRFZSME9CQllFRk5LZQpBVUZYc2Z2N2lML0lYVVBXdzY2ZU5jQnhNQjhHQTFVZEl3UVlNQmFBRlB0NFR4TDVZQldETEo4WGZ6UVpzeTQyCjZrR0pNR1lHQ0NzR0FRVUZCd0VCQkZvd1dEQWlCZ2dyQmdFRkJRY3dBWVlXYUhSMGNEb3ZMekV5Tnk0d0xqQXUKTVRvME1EQXlMekF5QmdnckJnRUZCUWN3QW9ZbWFIUjBjRG92THpFeU55NHdMakF1TVRvME1EQXdMMkZqYldVdgphWE56ZFdWeUxXTmxjblF3T1FZRFZSMFJCREl3TUlJS2JHOWpZV3d4TG1OdmJZSVFkR1Z6ZERFdWJHOWpZV3d4CkxtTnZiWUlRZEdWemRESXViRzlqWVd3eExtTnZiVEFuQmdOVkhSOEVJREFlTUJ5Z0dxQVloaFpvZEhSd09pOHYKWlhoaGJYQnNaUzVqYjIwdlkzSnNNR0VHQTFVZElBUmFNRmd3Q0FZR1o0RU1BUUlCTUV3R0F5b0RCREJGTUNJRwpDQ3NHQVFVRkJ3SUJGaFpvZEhSd09pOHZaWGhoYlhCc1pTNWpiMjB2WTNCek1COEdDQ3NHQVFVRkJ3SUNNQk1NCkVVUnZJRmRvWVhRZ1ZHaHZkU0JYYVd4ME1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ3A0Q2FxZlR4THNQTzQKS2JueDJZdEc4bTN3MC9keTVVR1VRNjZHbGxPVTk0L2I0MmNhbTRuNUZrTWlpZ01IaUx4c2JZVXh0cDZKQ3R5cQpLKzFNcDFWWEtSTTVKbFBTNWRIaWhxdHk1U3BrTUhjampwQSs3U2YyVWtoNmpKRWYxTUVJY2JnWnpJRk5IT0hYClVUUUppVFhKcno3blJDZnlQWFZtbWErUGtIRlU4R0VEVzJGOVptU1kzVFBiQWhiWkV2UkZubjUrR1lxbkZuancKWWw3Y0I2MXYwRzVpOGQwbnVvbTB4a2hiNTU3Y3BiZHhLblhsaFU4N2RZSTR5SUdPdUFGUWpYcXFXN2NIZCtXUQpWSDB2dFA3cEgrRmt2YnY4WkkxMHMrNU5ZcCtzZjFQZGQxekJsRmdNSGF3dnFFYUg3SU9sejdkajlCdmtVc0dpClhxQWVqQnFPCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVpakNDQTNLZ0F3SUJBZ0lDRWswd0RRWUpLb1pJaHZjTkFRRUxCUUF3S3pFcE1DY0dBMVVFQXd3Z1kyRmoKYTJ4cGJtY2dZM0o1Y0hSdlozSmhjR2hsY2lCbVlXdGxJRkpQVDFRd0hoY05NVFV4TURJeE1qQXhNVFV5V2hjTgpNakF4TURFNU1qQXhNVFV5V2pBZk1SMHdHd1lEVlFRREV4Um9ZWEJ3ZVNCb1lXTnJaWElnWm1GclpTQkRRVENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUlLUjNtYUJjVVNzbmNYWXpRVDEzRDUKTnIrWjNtTHhNTWgzVFVkdDZzQUNtcWJKMGJ0UmxnWGZNdE5MTTJPVTFJNmEzSnUrdElaU2RuMnYyMUpCd3Z4VQp6cFpRNHp5MmNpbUlpTVFEWkNRSEp3ekM5R1puOEhhVzA5MWl6OUgwR28zQTdXRFh3WU5tc2RMTlJpMDBvMTRVCmpvYVZxYVBzWXJaV3ZSS2FJUnFhVTBoSG1TMEFXd1FTdk4vOTNpTUlYdXlpd3l3bWt3S2JXbm54Q1EvZ3NjdEsKRlV0Y05yd0V4OVdnajZLbGh3RFR5STFRV1NCYnhWWU55VWdQRnpLeHJTbXdNTzB5TmZmN2hvK1FUOXg1K1kvNwpYRTU5UzRNYzRaWHhjWEtldy9nU2xOOVU1bXZUK0QyQmhEdGtDdXBkZnNaTkNRV3AyN0ErYi9EbXJGSTlOcXNDCkF3RUFBYU9DQWNJd2dnRytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3UXdZRFZSMGVCRHd3T3FFNE1BYUMKQkM1dGFXd3dDb2NJQUFBQUFBQUFBQUF3SW9jZ0FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpBQUFBQUFBd0RnWURWUjBQQVFIL0JBUURBZ0dHTUg4R0NDc0dBUVVGQndFQkJITXdjVEF5QmdnckJnRUZCUWN3CkFZWW1hSFIwY0RvdkwybHpjbWN1ZEhKMWMzUnBaQzV2WTNOd0xtbGtaVzUwY25WemRDNWpiMjB3T3dZSUt3WUIKQlFVSE1BS0dMMmgwZEhBNkx5OWhjSEJ6TG1sa1pXNTBjblZ6ZEM1amIyMHZjbTl2ZEhNdlpITjBjbTl2ZEdOaAplRE11Y0Rkak1COEdBMVVkSXdRWU1CYUFGT21rUCs2ZXBlYnkxZGQ1WUR5VHBpNGtqcGVxTUZRR0ExVWRJQVJOCk1Fc3dDQVlHWjRFTUFRSUJNRDhHQ3lzR0FRUUJndDhUQVFFQk1EQXdMZ1lJS3dZQkJRVUhBZ0VXSW1oMGRIQTYKTHk5amNITXVjbTl2ZEMxNE1TNXNaWFJ6Wlc1amNubHdkQzV2Y21jd1BBWURWUjBmQkRVd016QXhvQytnTFlZcgphSFIwY0RvdkwyTnliQzVwWkdWdWRISjFjM1F1WTI5dEwwUlRWRkpQVDFSRFFWZ3pRMUpNTG1OeWJEQWRCZ05WCkhRNEVGZ1FVKzNoUEV2bGdGWU1zbnhkL05CbXpMamJxUVlrd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFBMFkKQWVMWE9rbHg0aGhDaWtVVWwrQmRuRmZuMWcwVzVBaVFMVk5JT0w2UG5xWHUwd2puaE55aHFkd25maFlNbm95NAppZFJoNGxCNnB6OEdmOXBubExkL0RuV1NWM2dTKy9JL21BbDFkQ2tLYnk2SDJWNzkwZTZJSG1JSzJLWW0zam0rClUrK0ZJZEdwQmRzUVRTZG1pWC9yQXl1eE1ETTBhZE1rTkJ3VGZRbVpRQ3o2bkdIdzFRY1NQWk12WnBzQzhTa3YKZWt6eHNqRjFvdE9yTVVQTlBRdnRUV3JWeDhHbFIycWZ4LzR4YlFhMXYyZnJOdkZCQ21PNTlnb3oram5XdmZUdApqMk5qd0RaN3ZsTUJzUG0xNmRiS1lDODQwdXZSb1pqeHFzZGMzQ2hDWmpxaW1GcWxORy94b1BBOCtkVGljWnpDClhFOWlqUEljdlc2eTFhYTNiR3c9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K", 11 | "Key": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS1FJQkFBS0NBZ0VBdVNoTTR4enF6cE5YcFNaNnAvZnQrRmt5VmgyK1BSZXJUelV0OERRSng2UkVjQS9FCnN2RnNIVmNOSkZMS2twYTNlOEd3SUZBakJQNnJPK3hoR1JjWlJrdENON1gyOW5LZFhGbHZkYzJxd0hyTFF5WWkKTTB3ODhTck41VERiNi96TWU2dTB0dERiYWtDbDd6ZEJKUXJ6a1h5ZU1MeVkzTUs3aVkrMHpwL2JqMVhvbk5DdQpaQStkZ3hsMVNrV01DVUYvQk9HNWFyT1hwb0x4S0dQWGdzV3hOTVNLVmJKSHczL3ZqNTViZU92Um5lT3BNWlhvCmMwOWpZT3VBakNka1Z5czBSWHJLNWNCRDRMbVRXdnN4MFdTK2VMVHlGTTdQTHVZM3lEWkNNWEhjVmlqRHhnbFMKYjB1ZVRQcGFUWEQwYkxqZ0RNOUVEdE15ZEJzMUNPWlpPWG9ickN5Q2I1eWxTOFdVd1NzVXM1UldxZnlVbnAvcgpSNGx2c2RZOWRVZjRPdkNMVnJvWWk5NWFGc1Zxa0xLOExuL0Eyc3kxYWlDTnR4RmpKOXRXbWU0V0NhdzRoU0YvCkR4NWVNNWNYR2JSYXduVlZJQlZXeHhzNTBPMFJlUWRvbXBQZEFNS1RDWk9SRmxYaDdOWTdxQVdWRGtpdzhyam8Kekd3Ni9XdjlOR3hTNTliKzc0YVAxcjBxOTZ2RS9Rdi8zTCtjbjhiN0lBLytPYmFKdzhIT3RGbXc4RjBxQkN3MAprYWVVSloxb1JueGFYQUo4RHhHREpFOVdNUzh0QmJtVm16YkxoRkMzeDdVc0xGeTBrSzh1SFBFT3dQb2NKNUFUCkE1UHBvclNEMmFleHA0Z3VqYVp5c1JManpmY0dnaTdva0JFNlZVNWVqRE1iYS9lNERQNEJQUVg5VmtVQ0F3RUEKQVFLQ0FnQmZjMWdYcUp1ZmZMT3REcVlpbXh4UmIrSVVKT2NpWldaSndmZDVvY244NGtEcHFDZFZ2RUZvNnF4NgpzamQ5MURhb2xOUHdCSC9aSGxRMTR3aTNQNEluQzdzS0wwTXVEeTN5SXFUa0RPOWVwSzdPWWdVMWZyTFgvS0lCCjZlc2x2Ny9HYldFTzhhSjdKdktqM0U4NEFtcEg4UDgzenJIYTlJUnJTT3NEcmNNcEpEZHpSOXp1OW1IVDZMYmYKWC9UdC9KYTNkSW42YUxUZ0FSYkRKSjAvN0J3TFFOcXpqT0dUOWdzUWRhbGdMK2x5eEo4L1ViRndhRmVwNmgzdApvbzBHcHQ0ZWgwdTdueDhlNVd3Q2RnWmJsTnpnS3grMC9Gd3dLRHhQZVRFc2ZpOEJONmlkR2NjbVdzd3prTWdtCnJmbERaeGNSWTNRSlZIVHBCL0dTTWZXRFBPQ3dRdGltQk1WN3kxM2hPMTdPWXpSNDBMZnpUalJBbmtna2V2eWYKcFowb3dLR3o4QS9haHhRWWJmYVQ5VEhXV0wrYUpYeUhFanBKckp5aTg3UExVbzhsOFVydU56MDRWNXpLOFJPbgo2cG9EWmVtbm1EYWRlU09pK3hZRWlGT1NwSXNWbzlpcm9jUGFKN2YzYWpiNUU4RHpuN1o1MmhzL2R6akpLcFZJCm5mVDFkUU9SZEowSXRUNlRlQ2RTL0dpS25IS1RtNjR2T21IbmlJcm8rUGRhUmFjV0IrTUJ0VytRd0cyUStyRGkKc3g4NlpQbHRpTVpLMDZ5TVlyVHZUdGk2aFVGaUY5cWh4b3RGazdNQkNrZlIwYUVhaUREQUpKNm1jb1lpRUQ2QgpBVGJhVmpVaGNaUiswYkRST25PN0ozRk5rZmx3K2dMaVhvcXFRRW9pU2ZWb2h5SWY3UUtDQVFFQThjYTM5K0g4CjN3L2Qrcm0yUGNhM0RMQnBYaWU4Z3ZYcGpjazVYSkpvSGVmbnJjZWQrcFpXaTZEYncwYld0MEdtYkxmVjJNSlAKV2I1aTZzSXhmdkN3YlFqbHY0UnExMVA5ZEswT3poMnVpKzZ6cXVBMG5YTVcrN0lJS0cvdDhmS2NJZGRRNnRGcwpFclFVTFBDak56ODA2cHBiSlhPRmVvMW1BK293TGhHNlA3dDhCdlZHSk1NaTNxejNlSUNuVVE2eDNFY01ITXNuClhrM21DUzI1WUZaNk96cytFK254cGVraTAzZmQwblp3UE1jdElHZys1c3hleE9zREsrTHlvb2FqQnc5N0oyUzIKcUNNWXFtT0tLcmxEQ3Y1WmQ4dlZLN3hXVmpKRVhGTTNMZ2pieHBRcCtuVXNVVWxwS01LOVlGS0lRREl0RU9aMApWcWExTXJaOElzN1l5d0tDQVFFQXhBemZIa2pIVGlvTHdZbG5EcEk0MWlOTDh5Y0ZBallrTC94dWhPU2tlVkE4CjdRWDZPZUpDekR3Z0FUYXVqOWR6Y0wwby9yTndWV0xWcnQ3OXk3YnJvVDdFREZKWVNTY25GRXNMTlVWSXRncGkKckNSUXJTL1F2TkVGTmE5K0pRc1dmYkdBNHdIUTFaSjI4MFp1cWMvNlEyUi9kZVh3cUZBQVBHN2NIcEhHWlR6ZQoyRmFRUHFLRkV4WlEyZkpvRys0SVBRNHVQVERybXlGMmVUWXk2T3BaaDBHbWJRYlVTa1dFWDlQRmF1cHJIWVdGCk8wK25DaVVPNVRaMFZoaGR2dUNKMWdPclZHYzhBUlJtUVZ1aUNEWTZCaGlvVTU0ZmZsSXlDTXZ5a3MwcmRXZ3MKWVJ2TmN4TXNlRGJpTDRKSURkMHhiN1d4VUdmVjRVNHZPMks5Vms1N0x3S0NBUUVBMkd1eE1jcXd1RnRUc0tPYwpaaUFDcXZFZTRKRmhSVGtySHlnSW1MelZSaS9ZU3M1c3MycnZmWDA0T3N5bVZ0UUZUVHdoeUMzbktjWXFkVW52ClZGblBFMHJyblV2Qzk0elBUQ205SHZPaTBzK1JORndOdlFMUWgrME5NR1ZBOFZyaU44aXRQZ1RJWU5XaFdianQKNFA1TE45V0QwVHBmT1J4cFBRZmNxT0JsZjdjcmhtNzNvdUNwemZtMmE3OStCaWpKUFF5NzR1cFhDeXRmeHNlUApNSlU0Uk56NjdJaDFMclpKM2xGbDFvYitZT2xKazhDOHpZd1RLT0hWck9zeGxobyt4SXN2Q2t3MDFMelZ6Mi9hCnRmT3Y5NTlHSnQzbXE0ZWpJUFZPQy9iUlpmdTMvMEdSY2dpQTZ5SnpaM0VxWTVaOU1EbTU3VzdjcE5RRlRxZmEKNXEyUmtRS0NBUUErNGhZSzQ3TXg2aUNkTWxKaEJSdS82OUJucktOWm96NFdPalRFNFlXejk3MmpGU0Mrd2tsRQpzeUJjNDBvNGp4WFRHb2wwc04rZU03WndnY3dNTko3OXVHRXZ4cFhVMlA4YTdqc3BHaEVKZXVsTlo5U015R0orCnZkaWE4TEJZZDJiK2FCbjhOay9pd1Rqd0xTNC92NXI1Vk5uaFdpRElDK2tYZVVPWGRwQ1pWbDN3TEV2V0cxRHQKMzJHTmxzZzM5VENsVE5BZUJudjc1VTdYOEQrQ0gvRVpoa0E0aGxFL2hXN0JRZTczclRzd1creHhLc3BjWWFpVwpjdEg3NzVMYUw3Rm1lUVRTYk01OVZpcTZXZ2J0OVY3Rko5R09DSkQzZHF2ZjBITDlEVndjSzQ3WWt3OWlFc3RYCnY5cnEvREhhYUpGNzBGNlFlTTNNbDhSa212WTZJYkEzQW9JQkFRRGt6RmZLeG9HQ3dWUDlua3k4NmFQSjFvd2kKc2FDZEx6RjRWTENRZzkrUXJITzEyY0p5MFFQUnJ2cUQyMGp1cDFlOWJhWVZzbkdYc1FZTFg2NVR6UzJSSCtlSAp6S0NPTTdnMVE3djMxNWpjMDMvN1lQck4rb3RrV0VBOUkyaDZjUE1vY3c0aERTNk02OFlxQVlKTS9RclVhenZhCnhBTFJaZEVkQW1xWDA4VHhuY1hRUEVxYkk0ZnlSZ2pVM1BYR3RRaFFFbERpR2kwbThjQTJNTXdsR1RmbTdOSXgKaENjZ2ZkL296TEp2VUhiMkxLRi82cXEySmJVRHlOMkVoK0xSZUJjdnp6Y1grZE5MdGQxY0Uvcm1SM2hMbWxmNgo3KzRpTVMxK0t1eWV3VlJVUEE1c1F1aUYyVUVoeEs1MUpZK1FpOG9HbERKdGRrOXB3QlZNN1F0WW9KVEwKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /traefik.sample.toml: -------------------------------------------------------------------------------- 1 | defaultEntryPoints = ["https","http"] 2 | 3 | [api] 4 | entryPoint = "traefik" 5 | dashboard = true 6 | 7 | [entryPoints] 8 | [entryPoints.http] 9 | address = ":80" 10 | [entryPoints.http.redirect] 11 | entryPoint = "https" 12 | [entryPoints.https] 13 | address = ":443" 14 | [entryPoints.https.tls] 15 | minVersion = "VersionTLS12" 16 | cipherSuites = [ 17 | "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", 18 | "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 19 | "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 20 | "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 21 | "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 22 | "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" 23 | ] 24 | [entryPoints.traefik] 25 | address = ":8080" 26 | [entryPoints.traefik.auth.basic] 27 | users = ["admin:{SHA}jLIjfQZ5yojbZGTqxg2pY0VROWQ="] 28 | 29 | [acme] 30 | email = "{{ EMAIL }}" 31 | storage = "/etc/traefik/acme/acme.json" 32 | entryPoint = "https" 33 | onHostRule = true 34 | [acme.tlsChallenge] 35 | 36 | [docker] 37 | endpoint = "unix:///var/run/docker.sock" 38 | domain = "{{ DOMAIN }}" 39 | watch = true 40 | exposedbydefault = false 41 | --------------------------------------------------------------------------------