├── LICENSE ├── README.md ├── defaults └── main.yml ├── handlers └── main.yml ├── meta └── main.yml ├── tasks └── main.yml ├── templates ├── 10-auth.conf.j2 ├── 10-director.conf.j2 ├── 10-logging.conf.j2 ├── 10-mail.conf.j2 ├── 10-master.conf.j2 ├── 10-ssl.conf.j2 ├── 15-lda.conf.j2 ├── 15-mailboxes.conf.j2 ├── 20-imap.conf.j2 ├── 20-managesieve.conf.j2 ├── 90-plugin.conf.j2 ├── 90-sieve-extprograms.conf.j2 ├── 90-sieve.conf.j2 ├── auth-passwdfile.conf.ext.j2 ├── auth-static.conf.ext.j2 ├── blacklist-recipients.j2 ├── dkim_signing.conf.j2 ├── domains.j2 ├── dovecot.conf.j2 ├── global-default.sieve.j2 ├── ip_whitelist.map.j2 ├── multimap.conf.j2 ├── passwd.j2 ├── report-ham.sieve.j2 ├── report-spam.sieve.j2 ├── sa-learn-ham.sh.j2 ├── sa-learn-spam.sh.j2 ├── smtpd.conf.j2 ├── virtuals.j2 └── whitelist.sender.domain.map.j2 ├── tests ├── inventory └── test.yml └── vars └── main.yml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 gonzalo@x61.sh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ansible role for a Mailserver 2 | ============================= 3 | 4 | Ansible role to create a Mailserver on OpenBSD (>=7.5 & -current) with OpenSMTPD, Dovecot and Rspamd. 5 | 6 | Requirements 7 | ------------ 8 | 9 | OpenBSD, Python 3 (on client machine) and 10 minutes. 10 | 11 | Notes 12 | ----- 13 | 14 | This is still a WIP, so far, you need to create DKIM keys with rspamd (https://rspamd.com/doc/modules/dkim_signing.html), 15 | new users and DNS entries. Also, you need to enable dovecot, smtpd and rspamd at boot. 16 | 17 | You need to adjust your pf.conf (example bellow). 18 | 19 | Also, you need to delete examples on /etc/mail/virtuals and /etc/mail/domains 20 | 21 | Feedback is welcome. 22 | 23 | Example pf.conf 24 | --------------- 25 | 26 | For IMAPs I like to keep bruteforce people away with some tweaks on pf.conf, keep in mind that 27 | lower numbers than that, can couse problems checking emails on huge directories like misc or mailing list. 28 | 29 | ``` 30 | ... 31 | pass in quick on egress proto tcp from any \ 32 | to (egress) port imaps \ 33 | flags S/SA modulate state \ 34 | (max-src-conn 50, max-src-conn-rate 50/5, overload flush global) 35 | ... 36 | ``` 37 | 38 | For SMTP I have pretty the same: 39 | 40 | ``` 41 | ... 42 | pass in quick log (to pflog1) proto tcp from any \ 43 | to (egress) port smtp 44 | 45 | pass in quick log (to pflog1) proto tcp from any \ 46 | to (egress) port { submission, smtps } \ 47 | flags S/SA modulate state \ 48 | (max-src-conn 50, max-src-conn-rate 25/5, overload flush global) 49 | ... 50 | ``` 51 | 52 | Example Ansible 53 | --------------- 54 | 55 | This example is for a remote setup, so ,,test'' is your future mailserver, you 56 | already put your ssh key on ,,test'' and this server already have python3.8 57 | installed. 58 | 59 | ``` 60 | $ doas pkg_add ansible 61 | ... 62 | $ cd /tmp && mkdir ansible && cd ansible 63 | $ git clone https://github.com/gonzalo-/ansible-role-mailserver 64 | ... 65 | ... 66 | ... 67 | $ mv ansible-role-mailserver gonzalo-.mailserver 68 | $ cat hosts 69 | test ansible_python_interpreter=/usr/local/bin/python3.8 70 | $ cat mailserver.yml 71 | --- 72 | - hosts: test 73 | roles: 74 | - role: gonzalo-.mailserver 75 | become: yes 76 | become_method: doas 77 | 78 | vars: 79 | domain: 'foobar.com' 80 | mail_dir: '/var/vmail' 81 | mail_user: 'gonzalo' 82 | release: '7.5' 83 | arch: 'amd64' 84 | installurl_mirror: 'https://cdn.openbsd.org/pub/OpenBSD/' 85 | pkg_path: 'https://cdn.openbsd.org/pub/OpenBSD/{{ release }}/packages/{{ arch }}/' 86 | packages_list: 87 | - dovecot 88 | - dovecot-pigeonhole 89 | - opensmtpd-extras 90 | - opensmtpd-filter-rspamd 91 | - opensmtpd-filter-senderscore 92 | - rspamd 93 | $ ansible-playbook -i hosts mailserver.yml 94 | ...MAGIC... 95 | $ 96 | ``` 97 | 98 | Example Playbook 99 | ---------------- 100 | ``` 101 | --- 102 | - hosts: test 103 | roles: 104 | - role: gonzalo-.mailserver 105 | become: yes 106 | become_method: doas 107 | 108 | vars: 109 | domain: 'foobar.com' 110 | mail_dir: '/var/vmail' 111 | mail_user: 'gonzalo' 112 | release: '7.5' 113 | arch: 'amd64' 114 | installurl_mirror: 'https://cdn.openbsd.org/pub/OpenBSD/' 115 | pkg_path: 'https://cdn.openbsd.org/pub/OpenBSD/{{ release }}/packages/{{ arch }}/' 116 | packages_list: 117 | - dovecot 118 | - dovecot-pigeonhole 119 | - opensmtpd-extras 120 | - opensmtpd-filter-rspamd 121 | - opensmtpd-filter-senderscore 122 | - rspamd 123 | ``` 124 | 125 | Enable Spam Learning with Dovecot Antispam 126 | ------------------------------------------ 127 | The rspamd system can be trained to learn spam (or ham) by checking if users move 128 | their email from a folder in their Inbox to the Spam folder (to flag as spam), or 129 | the reverse: move incorrectly flagged spam out of the Spam folder. This will incur 130 | some overhead when it comes to moving messages, but it can enable the system to learn 131 | spam/ham more efficiently. 132 | 133 | Note: This ONLY works with IMAP 134 | 135 | To enable, modify the following line in /etc/dovecot/conf.d/20-imap.conf: 136 | ``` 137 | mail_plugins = $mail_plugins imap_sieve 138 | ``` 139 | 140 | Also edit /etc/dovecot/conf.d/90-plugin.conf if you want to enable more logging 141 | or to change the default Spam and Trash folders (if they are different on your system) 142 | and then restart dovecot with: ```rcctl restart dovecot``` 143 | 144 | 145 | Author Information 146 | ------------------ 147 | 148 | https://x61.sh/ 149 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for ansible-rol-mailserver -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for ansible-rol-mailserver -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: gonzalo- 4 | description: Role to setup a mailserver with opensmtpd, dovecot, dkimproxy and rspamd. 5 | license: BSD 6 | min_ansible_version: 1.9 7 | galaxy_tags: 8 | - openbsd 9 | - system 10 | - opensmtpd 11 | - opensmtpd-extras 12 | - mailserver 13 | - rspamd 14 | - dovecot 15 | - dovecot-pigeonhole 16 | - dovecot-antispam 17 | - dkimproxy 18 | - antispam 19 | platforms: 20 | - name: OpenBSD 21 | versions: 22 | - 7.2 23 | - 7.1 24 | - 7.0 25 | - 6.9 26 | - 6.8 27 | - 6.7 28 | - 6.6 29 | dependencies: [] 30 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: set installurl 4 | lineinfile: 5 | dest=/etc/installurl 6 | line="{{ installurl_mirror }}" 7 | insertafter=EOF 8 | create=True 9 | 10 | - group: 11 | name: vmail 12 | state: present 13 | 14 | - user: 15 | name: vmail 16 | comment: "Virtual Mail" 17 | shell: /sbin/nologin 18 | home: "{{ mail_dir }}" 19 | group: vmail 20 | 21 | - sieve: 22 | name: sieve-global 23 | file: path=/var/sieve state=directory 24 | group: vmail 25 | 26 | - name: Installing packages 27 | openbsd_pkg: name={{ item }} state=present 28 | with_items: "{{ packages_list }}" 29 | 30 | - template: src=smtpd.conf.j2 dest="/etc/mail/smtpd.conf" owner="root" group="wheel" mode="0644" 31 | - template: src=blacklist-recipients.j2 dest="/etc/mail/blacklist-recipients" owner="root" group="wheel" mode="0644" 32 | - template: src=domains.j2 dest="/etc/mail/domains" owner="root" group="wheel" mode="0644" 33 | - template: src=virtuals.j2 dest="/etc/mail/virtuals" owner="root" group="wheel" mode="0644" 34 | - template: src=passwd.j2 dest="/etc/mail/passwd" owner="_dovecot" group="_smtpd" mode="0440" 35 | - template: src=dkimproxy_in.conf.j2 dest="/etc/dkimproxy_in.conf" owner="root" group="wheel" mode="0644" 36 | - template: src=dkimproxy_out.conf.j2 dest="/etc/dkimproxy_out.conf" owner="root" group="wheel" mode="0644" 37 | - template: src=90-plugin.conf.j2 dest="/etc/dovecot/conf.d/90-plugin.conf" owner="root" group="wheel" mode="0644" 38 | - template: src=90-sieve.conf.j2 dest="/etc/dovecot/conf.d/90-sieve.conf" owner="root" group="wheel" mode="0644" 39 | - template: src=10-auth.conf.j2 dest="/etc/dovecot/conf.d/10-auth.conf" owner="root" group="wheel" mode="0644" 40 | - template: src=20-imap.conf.j2 dest="/etc/dovecot/conf.d/20-imap.conf" owner="root" group="wheel" mode="0644" 41 | - template: src=15-lda.conf.j2 dest="/etc/dovecot/conf.d/15-lda.conf" owner="root" group="wheel" mode="0644" 42 | - template: src=15-mailboxes.conf.j2 dest="/etc/dovecot/conf.d/15-mailboxes.conf.j2" owner="root" group="wheel" mode="0644" 43 | - template: src=10-director.conf.j2 dest="/etc/dovecot/conf.d/10-director" owner="root" group="wheel" mode="0644" 44 | - template: src=20-managesieve.conf.j2 dest="/etc/dovecot/conf.d/20-managesieve.conf" owner="root" group="wheel" mode="0644" 45 | - template: src=10-mail.conf.j2 dest="/etc/dovecot/conf.d/10-mail.conf" owner="root" group="wheel" mode="0644" 46 | - template: src=10-master.conf.j2 dest="/etc/dovecot/conf.d/10-master.conf" owner="root" group="wheel" mode="0644" 47 | - template: src=auth-passwdfile.conf.ext.j2 dest="/etc/dovecot/conf.d/auth-passwdfile.conf.ext" owner="root" group="wheel" mode="0644" 48 | - template: src=dovecot.conf.j2 dest="/etc/dovecot/dovecot.conf" owner="root" group="wheel" mode="0644" 49 | - template: src=10-logging.conf.j2 dest="/etc/dovecot/conf.d/10-logging.conf" owner="root" group="wheel" mode="0644" 50 | - template: src=10-ssl.conf.j2 dest="/etc/dovecot/conf.d/10-ssl.conf" owner="root" group="wheel" mode="0644" 51 | - template: src=auth-static.conf.ext.j2 dest="/etc/dovecot/conf.d/auth-static.conf.ext" owner="root" group="wheel" mode="0644" 52 | - template: src=global-default.sieve.j2 dest="/var/sieve/global-default.sieve" owner="root" group="wheel" mode="0644" 53 | - template: src=report-ham.sieve.j2 dest="/usr/local/lib/dovecot/sieve/report-ham.sieve" owner="root" group="wheel" mode="0644" 54 | - template: src=report-spam.sieve.j2 dest="/usr/local/lib/dovecot/sieve/report-spam.sieve" owner="root" group="wheel" mode="0644" 55 | - template: src=sa-learn-ham.sh.j2 dest="/usr/local/lib/dovecot/sieve/sa-learn-ham.sh" owner="_dovecot" group="wheel" mode="0755" 56 | - template: src=sa-learn-spam.sh.j2 dest="/usr/local/lib/dovecot/sieve/sa-learn-spam.sh" owner="_dovecot" group="wheel" mode="0755" 57 | - template: src=ip_whitelist.map.j2 dest="/etc/rspamd/local.d/ip_whitelist.map" owner="root" group="wheel" mode="0644" 58 | - template: src=multimap.conf.j2 dest="/etc/rspamd/local.d/multimap.conf" owner="root" group="wheel" mode="0644" 59 | - template: src=whitelist.sender.domain.map.j2 dest="/etc/rspamd/local.d/whitelist.sender.domain.map" owner="root" group="wheel" mode="0644" 60 | 61 | - blockinfile: | 62 | dest=/etc/login.conf backup=yes 63 | content="## Dovecot 64 | dovecot:\ 65 | :openfiles-cur=1024:\ 66 | :openfiles-max=2048:\ 67 | :tc=daemon:" 68 | insertafter=EOF 69 | 70 | - name: Enable smtpd 71 | service: name=smtpd state=started enabled=yes 72 | 73 | - name: Enable dovecot 74 | service: name=dovecot state=started enabled=yes 75 | 76 | - command: /usr/local/bin/sievec /usr/local/lib/dovecot/sieve/report-ham.sieve /usr/local/lib/dovecot/sieve/report-ham.svbin 77 | - command: /usr/local/bin/sievec /usr/local/lib/dovecot/sieve/report-spam.sieve /usr/local/lib/dovecot/sieve/report-spam.svbin 78 | 79 | - name: Enable rspamd 80 | service: name=rspamd state=started enabled=yes 81 | -------------------------------------------------------------------------------- /templates/10-auth.conf.j2: -------------------------------------------------------------------------------- 1 | auth_mechanisms = plain 2 | !include auth-passwdfile.conf.ext 3 | -------------------------------------------------------------------------------- /templates/10-director.conf.j2: -------------------------------------------------------------------------------- 1 | service imap-login { 2 | inet_listener imap { 3 | port = 0 4 | } 5 | } 6 | 7 | service pop3-login { 8 | inet_listener pop3 { 9 | port = 0 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /templates/10-logging.conf.j2: -------------------------------------------------------------------------------- 1 | auth_verbose = no 2 | #debug_log_path = /var/log/dovecot-debug.log 3 | mail_debug = no 4 | -------------------------------------------------------------------------------- /templates/10-mail.conf.j2: -------------------------------------------------------------------------------- 1 | mail_home = {{ mail_dir }}/%d/%n 2 | mbox_write_locks = fcntl 3 | namespace inbox { 4 | inbox = yes 5 | location = maildir:{{ mail_dir }}/%d/%n/Maildir:LAYOUT=fs 6 | mailbox Drafts { 7 | special_use = \Drafts 8 | } 9 | mailbox Spam { 10 | special_use = \Junk 11 | } 12 | mailbox Sent { 13 | special_use = \Sent 14 | } 15 | mailbox "Sent Messages" { 16 | special_use = \Sent 17 | } 18 | mailbox Trash { 19 | special_use = \Trash 20 | } 21 | prefix = 22 | separator = / 23 | } 24 | mmap_disable = yes 25 | -------------------------------------------------------------------------------- /templates/10-master.conf.j2: -------------------------------------------------------------------------------- 1 | service auth { 2 | unix_listener auth-userdb { 3 | group = vmail 4 | mode = 0666 5 | user = vmail 6 | } 7 | } 8 | 9 | service imap-login { 10 | inet_listener imap { 11 | #port = 143 12 | } 13 | inet_listener imaps { 14 | #port = 993 15 | #ssl = yes 16 | } 17 | 18 | # Number of connections to handle before starting a new process. Typically 19 | # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0 20 | # is faster. 21 | #service_count = 1 22 | 23 | # Number of processes to always keep waiting for more connections. 24 | #process_min_avail = 0 25 | 26 | # If you set service_count=0, you probably need to grow this. 27 | #vsz_limit = $default_vsz_limit 28 | } 29 | 30 | service pop3-login { 31 | inet_listener pop3 { 32 | #port = 110 33 | } 34 | inet_listener pop3s { 35 | #port = 995 36 | #ssl = yes 37 | } 38 | } 39 | 40 | service lmtp { 41 | unix_listener lmtp { 42 | #mode = 0666 43 | } 44 | 45 | # Create inet listener only if you can't use the above UNIX socket 46 | #inet_listener lmtp { 47 | # Avoid making LMTP visible for the entire internet 48 | #address = 49 | #port = 50 | #} 51 | } 52 | 53 | service imap { 54 | # Most of the memory goes to mmap()ing files. You may need to increase this 55 | # limit if you have huge mailboxes. 56 | #vsz_limit = $default_vsz_limit 57 | 58 | # Max. number of IMAP processes (connections) 59 | #process_limit = 1024 60 | } 61 | 62 | service pop3 { 63 | # Max. number of POP3 processes (connections) 64 | #process_limit = 1024 65 | } 66 | 67 | service auth-worker { 68 | # Auth worker process is run as root by default, so that it can access 69 | # /etc/shadow. If this isn't necessary, the user should be changed to 70 | # $default_internal_user. 71 | #user = root 72 | } 73 | 74 | service dict { 75 | # If dict proxy is used, mail processes should have access to its socket. 76 | # For example: mode=0660, group=vmail and global mail_access_groups=vmail 77 | unix_listener dict { 78 | #mode = 0600 79 | #user = 80 | #group = 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /templates/10-ssl.conf.j2: -------------------------------------------------------------------------------- 1 | #ssl = required 2 | #ssl_cert = =6.9 SNI is possible 6 | #pki another-domain.org cert "/etc/ssl/another-domain.org_fullchain.pem" 7 | #pki another-domain.org key "/etc/ssl/private/another-domain.org_private.pem" 8 | 9 | ## Filters 10 | filter check_dyndns phase connect match rdns regex { '.*\.dyn\..*', '.*\.dsl\..*' } \ 11 | disconnect "550 no residential connections" 12 | 13 | filter check_rdns phase connect match !rdns \ 14 | disconnect "550 no rDNS" 15 | 16 | filter check_fcrdns phase connect match !fcrdns \ 17 | disconnect "550 no FCrDNS" 18 | 19 | filter senderscore \ 20 | proc-exec "filter-senderscore -blockBelow 10 -junkBelow 70 -slowFactor 5000" 21 | 22 | filter rspamd proc-exec "filter-rspamd" 23 | 24 | ## Tables 25 | table aliases file:/etc/mail/aliases 26 | table domains file:/etc/mail/domains 27 | table passwd passwd:/etc/mail/passwd 28 | table virtuals file:/etc/mail/virtuals 29 | table blacklist-recipients file:/etc/mail/blacklist-recipients 30 | 31 | ## Limits 32 | smtp ciphers "HIGH:!aNULL:!TLSv1:!MD5:!RC4:!GOST89MAC:@STRENGTH" 33 | smtp max-message-size 90M 34 | 35 | ## Ports 36 | listen on all tls pki {{ domain }} hostname {{ domain }} \ 37 | filter { check_dyndns, check_rdns, check_fcrdns, senderscore, rspamd } 38 | listen on all smtps pki {{ domain }} hostname {{ domain }} \ 39 | auth filter rspamd 40 | listen on all port submission tls-require pki {{ domain }} \ 41 | hostname {{ domain }} auth mask-src filter rspamd 42 | 43 | ## Actions 44 | action "mda_with_aliases" mda \ 45 | "/usr/local/libexec/dovecot/dovecot-lda -f %{sender} -d %{dest}" \ 46 | alias user vmail 47 | 48 | action "mda_with_virtuals" mda \ 49 | "/usr/local/libexec/dovecot/dovecot-lda -f %{sender} -d %{dest}" \ 50 | virtual user vmail 51 | 52 | action "mda_without_rspamd" mda \ 53 | "/usr/local/libexec/dovecot/dovecot-lda -f %{sender} -d %{dest}" \ 54 | virtual user vmail 55 | 56 | action "relay" relay 57 | 58 | match from any mail-from for domain reject 59 | match for local action "mda_with_virtuals" 60 | match auth from any for domain action "mda_without_rspamd" 61 | match from any for domain action "mda_with_virtuals" 62 | match from local for any action "relay" 63 | match auth from any for any action "relay" 64 | -------------------------------------------------------------------------------- /templates/virtuals.j2: -------------------------------------------------------------------------------- 1 | abuse@{{ domain }} {{ mail_user }}@{{ domain }} 2 | postmaster@{{ domain }} {{ mail_user }}@{{ domain }} 3 | webmaster@{{ domain }} {{ mail_user }}@{{ domain }} 4 | root@{{ domain }} {{ mail_user }}@{{ domain }} 5 | {{ mail_user }}@{{ domain }} vmail 6 | 7 | user2@foobar.org {{ mail_user }}@{{ domain }} 8 | user2@foobar.org {{ mail_user }}@{{ domain }} 9 | root@foobar.org {{ mail_user }}@{{ domain }} 10 | -------------------------------------------------------------------------------- /templates/whitelist.sender.domain.map.j2: -------------------------------------------------------------------------------- 1 | paypal.com 2 | openbsd.org 3 | cvs.openbsd.org 4 | lists.openbsd.org 5 | -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | gather_facts: true 4 | become: True 5 | become_method: doas 6 | roles: 7 | - ansible-rol-mailserver 8 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | --------------------------------------------------------------------------------