diff options
Diffstat (limited to 'roles/dovecot')
38 files changed, 901 insertions, 0 deletions
diff --git a/roles/dovecot/defaults/main.yml b/roles/dovecot/defaults/main.yml new file mode 100644 index 0000000..e4f3842 --- /dev/null +++ b/roles/dovecot/defaults/main.yml @@ -0,0 +1,24 @@ +dovecot_recipient_delimiter: '+' +dovecot_default_user_quota: 5G +dovecot_quota_grace_percent: 5 +dovecot_default_domain: '{{ email_domain }}' + +dovecot_rspamd_host: '{{ rspamd_host }}' +dovecot_rspamd_password: '{{ rspamd_password }}' +dovecot_rspamd_pubkey: '{{ rspamd_pubkey }}' + +dovecot_access_group: role-imap-access + +dovecot_archive_on_calendar: weekly + +dovecot_lmtp_port: 24 +dovecot_quota_status_port: 10993 + +dovecot_tika_port: 9998 +dovecot_solr_port: 8983 + +dovecot_max_mail_size: 64M +dovecot_quota_warning_percent: + - 95 + - 90 + - 80 diff --git a/roles/dovecot/files/etc/dovecot/sieve.before.d/10-rspamd.sieve b/roles/dovecot/files/etc/dovecot/sieve.before.d/10-rspamd.sieve new file mode 100644 index 0000000..7931a71 --- /dev/null +++ b/roles/dovecot/files/etc/dovecot/sieve.before.d/10-rspamd.sieve @@ -0,0 +1,5 @@ +require ["fileinto"]; + +if header :is "X-Spam" "Yes" { + fileinto "Junk"; +} diff --git a/roles/dovecot/files/etc/dovecot/sieve/report-ham.sieve b/roles/dovecot/files/etc/dovecot/sieve/report-ham.sieve new file mode 100644 index 0000000..578e7b2 --- /dev/null +++ b/roles/dovecot/files/etc/dovecot/sieve/report-ham.sieve @@ -0,0 +1,15 @@ +require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; + +if environment :matches "imap.mailbox" "*" { + set "mailbox" "${1}"; +} + +if string "${mailbox}" "Trash" { + stop; +} + +if environment :matches "imap.email" "*" { + set "email" "${1}"; +} + +pipe :copy "report-ham.sh" [ "${email}" ]; diff --git a/roles/dovecot/files/etc/dovecot/sieve/report-spam.sieve b/roles/dovecot/files/etc/dovecot/sieve/report-spam.sieve new file mode 100644 index 0000000..d34c71b --- /dev/null +++ b/roles/dovecot/files/etc/dovecot/sieve/report-spam.sieve @@ -0,0 +1,7 @@ +require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; + +if environment :matches "imap.email" "*" { + set "email" "${1}"; +} + +pipe :copy "report-spam.sh" [ "${email}" ]; diff --git a/roles/dovecot/files/etc/dovecot/virtual/All Messages/dovecot-virtual b/roles/dovecot/files/etc/dovecot/virtual/All Messages/dovecot-virtual new file mode 100644 index 0000000..a7f3148 --- /dev/null +++ b/roles/dovecot/files/etc/dovecot/virtual/All Messages/dovecot-virtual @@ -0,0 +1,2 @@ +* + all diff --git a/roles/dovecot/files/etc/dovecot/virtual/Flagged/dovecot-virtual b/roles/dovecot/files/etc/dovecot/virtual/Flagged/dovecot-virtual new file mode 100644 index 0000000..883f49e --- /dev/null +++ b/roles/dovecot/files/etc/dovecot/virtual/Flagged/dovecot-virtual @@ -0,0 +1,2 @@ +* + flagged diff --git a/roles/dovecot/files/etc/dovecot/virtual/INBOX/dovecot-virtual b/roles/dovecot/files/etc/dovecot/virtual/INBOX/dovecot-virtual new file mode 100644 index 0000000..139e4b0 --- /dev/null +++ b/roles/dovecot/files/etc/dovecot/virtual/INBOX/dovecot-virtual @@ -0,0 +1,2 @@ +Virtual/All Messages + inthread refs x-mailbox INBOX diff --git a/roles/dovecot/files/etc/systemd/system/dovecot.service.d/override.conf b/roles/dovecot/files/etc/systemd/system/dovecot.service.d/override.conf new file mode 100644 index 0000000..0e524e8 --- /dev/null +++ b/roles/dovecot/files/etc/systemd/system/dovecot.service.d/override.conf @@ -0,0 +1,6 @@ +[Unit] +Wants=gssproxy.service +After=local-fs.target network-online.target dovecot-init.service gssproxy.service + +[Service] +Environment=GSS_USE_PROXY=yes diff --git a/roles/dovecot/files/var/lib/solr/dovecot/conf/schema.xml b/roles/dovecot/files/var/lib/solr/dovecot/conf/schema.xml new file mode 100644 index 0000000..601a290 --- /dev/null +++ b/roles/dovecot/files/var/lib/solr/dovecot/conf/schema.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<schema name="dovecot" version="2.0"> + <fieldType name="string" class="solr.StrField" omitNorms="true" sortMissingLast="true"/> + <fieldType name="long" class="solr.LongPointField" positionIncrementGap="0"/> + <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/> + + <fieldType name="text" class="solr.TextField" autoGeneratePhraseQueries="true" positionIncrementGap="100"> + <analyzer type="index"> + <tokenizer class="solr.StandardTokenizerFactory"/> + <filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/> + <filter class="solr.WordDelimiterGraphFilterFactory" catenateNumbers="1" generateNumberParts="1" splitOnCaseChange="1" generateWordParts="1" splitOnNumerics="1" catenateAll="1" catenateWords="1"/> + <filter class="solr.FlattenGraphFilterFactory"/> + <filter class="solr.LowerCaseFilterFactory"/> + <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> + <filter class="solr.PorterStemFilterFactory"/> + </analyzer> + <analyzer type="query"> + <tokenizer class="solr.StandardTokenizerFactory"/> + <filter class="solr.SynonymGraphFilterFactory" expand="true" ignoreCase="true" synonyms="synonyms.txt"/> + <filter class="solr.FlattenGraphFilterFactory"/> + <filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/> + <filter class="solr.WordDelimiterGraphFilterFactory" catenateNumbers="1" generateNumberParts="1" splitOnCaseChange="1" generateWordParts="1" splitOnNumerics="1" catenateAll="1" catenateWords="1"/> + <filter class="solr.LowerCaseFilterFactory"/> + <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> + <filter class="solr.PorterStemFilterFactory"/> + </analyzer> + </fieldType> + + <field name="id" type="string" indexed="true" required="true" stored="true"/> + <field name="uid" type="long" indexed="true" required="true" stored="true"/> + <field name="box" type="string" indexed="true" required="true" stored="true"/> + <field name="user" type="string" indexed="true" required="true" stored="true"/> + + <field name="hdr" type="text" indexed="true" stored="false"/> + <field name="body" type="text" indexed="true" stored="false"/> + + <field name="from" type="text" indexed="true" stored="false"/> + <field name="to" type="text" indexed="true" stored="false"/> + <field name="cc" type="text" indexed="true" stored="false"/> + <field name="bcc" type="text" indexed="true" stored="false"/> + <field name="subject" type="text" indexed="true" stored="false"/> + + <!-- Used by Solr internally: --> + <field name="_version_" type="long" indexed="true" stored="true"/> + + <uniqueKey>id</uniqueKey> +</schema> diff --git a/roles/dovecot/handlers/main.yml b/roles/dovecot/handlers/main.yml new file mode 100644 index 0000000..344cf91 --- /dev/null +++ b/roles/dovecot/handlers/main.yml @@ -0,0 +1,4 @@ +- name: restart dovecot + systemd: + name: dovecot + state: restarted diff --git a/roles/dovecot/meta/main.yml b/roles/dovecot/meta/main.yml new file mode 100644 index 0000000..c4f4b18 --- /dev/null +++ b/roles/dovecot/meta/main.yml @@ -0,0 +1,12 @@ +dependencies: + - role: yum + yum_repositories: + - epel + - rspamd + tags: yum + + - role: solr + tags: solr + + - role: tika + tags: tika diff --git a/roles/dovecot/tasks/freeipa.yml b/roles/dovecot/tasks/freeipa.yml new file mode 100644 index 0000000..1e1ee29 --- /dev/null +++ b/roles/dovecot/tasks/freeipa.yml @@ -0,0 +1,109 @@ +- name: create IMAP access group + ipagroup: + ipaadmin_principal: '{{ ipa_user }}' + ipaadmin_password: '{{ ipa_pass }}' + name: '{{ dovecot_access_group }}' + description: users with IMAP access + nonposix: yes + state: present + run_once: True + +- name: create service principals + ipaservice: + ipaadmin_principal: '{{ ipa_user }}' + ipaadmin_password: '{{ ipa_pass }}' + name: '{{ item }}/{{ ansible_fqdn }}' + state: present + loop: + - imap + - sieve + +- name: retrieve service keytabs + include_role: + name: freeipa_keytab + vars: + keytab_principal: '{{ item }}/{{ ansible_fqdn }}' + keytab_path: '{{ dovecot_keytab }}' + loop: + - imap + - sieve + +- name: configure gssproxy + include_role: + name: gssproxy_client + vars: + gssproxy_name: dovecot + gssproxy_section: service/dovecot + gssproxy_keytab: '{{ dovecot_keytab }}' + gssproxy_client_keytab: '{{ dovecot_keytab }}' + gssproxy_cred_usage: both + gssproxy_euid: dovecot + +- name: create SELinux policy for dovecot to access gssproxy + include_role: + name: selinux_policy + apply: + tags: selinux + vars: + selinux_policy_name: dovecot_gssproxy + selinux_policy_te: '{{ dovecot_selinux_policy_te }}' + tags: selinux + +- name: generate PAM configuration for dovecot + copy: + content: | + auth required pam_sss.so + account required pam_sss.so + dest: /etc/pam.d/dovecot + +- name: create HBAC service + ipahbacsvc: + ipaadmin_principal: '{{ ipa_user }}' + ipaadmin_password: '{{ ipa_pass }}' + name: '{{ dovecot_hbac_service }}' + description: Dovecot IMAP server + state: present + run_once: True + +- name: create imap-servers hostgroup + ipahostgroup: + ipaadmin_principal: '{{ ipa_user }}' + ipaadmin_password: '{{ ipa_pass }}' + name: '{{ dovecot_hbac_hostgroup }}' + description: IMAP Servers + host: "{{ groups[dovecot_hbac_hostgroup] | map('regex_replace', '$', '.' ~ ansible_domain) }}" + state: present + run_once: True + +# Note: we explicitly allow all here. SSSD will only be consulted when a user performs +# a PLAIN login, falling back to PAM authentication. Users with a valid Kerberos ticket +# bypass the PAM stack entirely, so a restrictive HBAC rule is pointless. +- name: create HBAC rule + ipahbacrule: + ipaadmin_principal: '{{ ipa_user }}' + ipaadmin_password: '{{ ipa_pass }}' + name: allow_dovecot_on_imap_servers + description: Allow IMAP on imap servers + hostgroup: + - '{{ dovecot_hbac_hostgroup }}' + usercategory: all + hbacsvc: + - '{{ dovecot_hbac_service }}' + run_once: True + +- name: create systemd override directory + file: + path: /etc/systemd/system/dovecot.service.d + state: directory + +- name: create systemd override file + copy: + src: etc/systemd/system/dovecot.service.d/override.conf + dest: /etc/systemd/system/dovecot.service.d/override.conf + notify: restart dovecot + register: dovecot_systemd_unit + +- name: reload systemd daemons + systemd: + daemon_reload: yes + when: dovecot_systemd_unit.changed diff --git a/roles/dovecot/tasks/main.yml b/roles/dovecot/tasks/main.yml new file mode 100644 index 0000000..09f2e2e --- /dev/null +++ b/roles/dovecot/tasks/main.yml @@ -0,0 +1,127 @@ +- name: install dovecot + dnf: + name: '{{ dovecot_packages }}' + state: present + +- name: add vmail user + user: + name: '{{ dovecot_vmail_user }}' + system: yes + home: '{{ dovecot_vmail_dir }}' + shell: /sbin/nologin + create_home: no + register: dovecot_vmail_user_result + +- name: create vmail directory + file: + path: '{{ dovecot_vmail_dir }}' + state: directory + owner: '{{ dovecot_vmail_user }}' + group: '{{ dovecot_vmail_user }}' + setype: mail_spool_t + mode: 0770 + +- name: set selinux context for vmail directory + sefcontext: + target: '{{ dovecot_vmail_dir }}(/.*)?' + setype: mail_spool_t + state: present + register: dovecot_vmail_sefcontext + +- name: apply selinux context to vmail directory + command: 'restorecon -R {{ dovecot_vmail_dir }}' + when: dovecot_vmail_sefcontext.changed + +- name: set up FreeIPA integration for IMAP + import_tasks: freeipa.yml + +- name: request TLS certificate + include_role: + name: getcert_request + vars: + certificate_service: imap + certificate_path: '{{ dovecot_certificate_path }}' + certificate_key_path: '{{ dovecot_certificate_key_path }}' + certificate_owner: dovecot + certificate_hook: systemctl reload dovecot + +- name: generate dhparams + openssl_dhparam: + path: '{{ dovecot_dhparams_path }}' + size: 2048 + +- name: configure Apache Solr for full-text search + import_tasks: solr.yml + tags: solr + +- name: create virtual config directory + file: + path: /etc/dovecot/virtual + state: directory + +- name: create global sieve directories + file: + path: '{{ item }}' + state: directory + recurse: yes + loop: + - '{{ dovecot_sieve_dir }}' + - '{{ dovecot_sieve_before_dir }}' + - '{{ dovecot_sieve_pipe_bin_dir }}' + +- name: create virtual mailbox definitions + copy: + src: etc/dovecot/virtual/ + dest: /etc/dovecot/virtual/ + +- name: generate dovecot configuration + template: + src: '{{ item.src }}' + dest: /etc/dovecot/{{ item.path | splitext | first }} + loop: "{{ lookup('filetree', '../templates/etc/dovecot', wantlist=True) }}" + loop_control: + label: '{{ item.path }}' + when: item.state == 'file' + notify: restart dovecot + +- name: copy quota warn script + template: + src: '{{ dovecot_quota_warning_script[1:] }}.j2' + dest: '{{ dovecot_quota_warning_script }}' + mode: 0555 + +- name: start dovecot + systemd: + name: dovecot + enabled: yes + state: started + +- import_tasks: rspamd.yml + +- name: open firewall ports + firewalld: + service: '{{ item }}' + permanent: yes + immediate: yes + state: enabled + loop: + - imaps + - managesieve + tags: firewalld + +- name: open firewall ports + firewalld: + port: '{{ item }}' + permanent: yes + immediate: yes + state: enabled + loop: + - '{{ dovecot_quota_status_port }}/tcp' + - '{{ dovecot_lmtp_port }}/tcp' + tags: firewalld + +- name: generate archive script + template: + src: '{{ dovecot_archive_script[1:] }}.j2' + dest: '{{ dovecot_archive_script }}' + mode: 0555 diff --git a/roles/dovecot/tasks/rspamd.yml b/roles/dovecot/tasks/rspamd.yml new file mode 100644 index 0000000..90686ee --- /dev/null +++ b/roles/dovecot/tasks/rspamd.yml @@ -0,0 +1,43 @@ +- name: install rspamd + dnf: + name: rspamd + state: present + +- name: copy rspamd X-SPAM sieve script + copy: + src: '{{ dovecot_sieve_before_dir[1:] }}/10-rspamd.sieve' + dest: '{{ dovecot_sieve_before_dir }}/10-rspamd.sieve' + register: dovecot_rspamd_sieve_script + +- name: compile rspamd X-SPAM sieve script + command: sievec '{{ dovecot_sieve_before_dir }}/10-rspamd.sieve' + when: dovecot_rspamd_sieve_script.changed + +- name: copy rspamd sieve reporting scripts + copy: + src: '{{ dovecot_sieve_dir[1:] }}/{{ item }}' + dest: '{{ dovecot_sieve_dir }}/{{ item }}' + loop: + - report-spam.sieve + - report-ham.sieve + register: dovecot_rspamd_report_sieve_scripts + +- name: compile rspamd sieve reporting scripts + command: sievec {{ dovecot_sieve_dir }}/{{ item }} + when: dovecot_rspamd_report_sieve_scripts.results[index].changed + loop: + - report-spam.sieve + - report-ham.sieve + loop_control: + index_var: index + +- name: generate rpsmad bash reporting scripts + template: + src: '{{ dovecot_sieve_pipe_bin_dir[1:] }}/{{ item }}.j2' + dest: '{{ dovecot_sieve_pipe_bin_dir }}/{{ item }}' + owner: root + group: dovecot + mode: 0550 + loop: + - report-spam.sh + - report-ham.sh diff --git a/roles/dovecot/tasks/solr.yml b/roles/dovecot/tasks/solr.yml new file mode 100644 index 0000000..0751192 --- /dev/null +++ b/roles/dovecot/tasks/solr.yml @@ -0,0 +1,40 @@ +- name: add solr collection for dovecot + command: + cmd: '{{ solr_install_dir }}/bin/solr create -c dovecot' + creates: '{{ solr_data_dir }}/dovecot' + become: True + become_user: solr + +- name: check if dovecot schema exists + stat: + path: '{{ solr_data_dir }}/dovecot/conf/schema.xml.bak' + register: schema_xml_bak + +- name: copy dovecot solr schema + copy: + src: '{{ solr_data_dir[1:] }}/dovecot/conf/schema.xml' + dest: '{{ solr_data_dir }}/dovecot/conf/schema.xml' + owner: solr + group: solr + register: solr_schema + changed_when: no + +- name: stat new schema + stat: + path: '{{ solr_data_dir }}/dovecot/conf/schema.xml' + register: schema_xml + +- name: remove managed-schema file + file: + path: '{{ solr_data_dir }}/dovecot/conf/managed-schema.xml' + state: absent + when: (not schema_xml_bak.stat.exists) or (schema_xml_bak.stat.checksum != schema_xml.stat.checksum) + notify: restart solr + +- name: generate dovecot solr config + template: + src: '{{ solr_data_dir[1:] }}/dovecot/conf/solrconfig.xml.j2' + dest: '{{ solr_data_dir }}/dovecot/conf/solrconfig.xml' + owner: solr + group: solr + notify: restart solr diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/10-auth.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/10-auth.conf.j2 new file mode 100644 index 0000000..2185d6d --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/10-auth.conf.j2 @@ -0,0 +1,10 @@ +auth_default_realm = {{ freeipa_realm }} + +auth_username_format = %Ln + +auth_gssapi_hostname = "$ALL" + +auth_mechanisms = gssapi plain login + +!include auth-system.conf.ext +!include auth-ldap.conf.ext diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/10-mail.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/10-mail.conf.j2 new file mode 100644 index 0000000..9a3884a --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/10-mail.conf.j2 @@ -0,0 +1,31 @@ +mail_location = mdbox:~/mdbox + +namespace inbox { + type = private + separator = / + inbox = yes + subscriptions = yes +} + +namespace virtual { + location = virtual:/etc/dovecot/virtual:INDEX=~/.virtual:CONTROL=~/.virtual:VOLATILEDIR=~/.virtual:LAYOUT=fs + + type = private + separator = / + prefix = Virtual/ +} + +mail_plugins = $mail_plugins quota virtual fts fts_solr + +mail_privileged_group = {{ dovecot_vmail_user }} + +first_valid_uid = {{ dovecot_vmail_user_result.uid }} +last_valid_uid = {{ dovecot_vmail_user_result.uid }} + +first_valid_gid = {{ dovecot_vmail_user_result.group }} +last_valid_gid = {{ dovecot_vmail_user_result.group }} + +# recommended configuration for quota:count +protocol !indexer-worker { + mail_vsize_bg_after_count = 100 +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/10-master.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/10-master.conf.j2 new file mode 100644 index 0000000..a2af8b3 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/10-master.conf.j2 @@ -0,0 +1,31 @@ +service imap-login { + inet_listener imap { + port = 143 + } + + inet_listener imaps { + port = 993 + ssl = yes + } +} + +service lmtp { + user = {{ dovecot_vmail_user }} + inet_listener lmtp { + port = {{ dovecot_lmtp_port }} + } +} + +service auth-worker { + user = $default_internal_user +} + +# Allow the vmail user to write to stats. This isn't strictly necessary, but +# prevents dovecot-lda from spamming the mail log with errors. +service stats { + unix_listener stats-writer { + user = dovecot + group = {{ dovecot_vmail_user }} + mode = 0660 + } +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/10-ssl.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/10-ssl.conf.j2 new file mode 100644 index 0000000..e677b44 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/10-ssl.conf.j2 @@ -0,0 +1,10 @@ +ssl = required + +ssl_cert = <{{ dovecot_certificate_path }} +ssl_key = <{{ dovecot_certificate_key_path }} + +ssl_dh = <{{ dovecot_dhparams_path }} + +ssl_min_protocol = TLSv1.2 + +ssl_cipher_list = {{ dovecot_ssl_cipher_list }} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/15-lda.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/15-lda.conf.j2 new file mode 100644 index 0000000..0ed20f5 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/15-lda.conf.j2 @@ -0,0 +1,10 @@ +recipient_delimiter = {{ dovecot_recipient_delimiter }} +lda_original_recipient_header = X-Original-To + +lda_mailbox_autocreate = yes + +lda_mailbox_autosubscribe = no + +protocol lda { + mail_plugins = $mail_plugins sieve +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/15-mailboxes.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/15-mailboxes.conf.j2 new file mode 100644 index 0000000..af47fcc --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/15-mailboxes.conf.j2 @@ -0,0 +1,36 @@ +namespace inbox { + + mailbox Drafts { + auto = subscribe + special_use = \Drafts + } + + mailbox Junk { + auto = subscribe + special_use = \Junk + } + + mailbox Trash { + auto = subscribe + special_use = \Trash + } + + mailbox Sent { + auto = subscribe + special_use = \Sent + } + + mailbox Archive { + auto = subscribe + special_use = \Archive + } + + # "auto = subscribe" on virtual folders causes dovecot to coredump. + mailbox "Virtual/All Messages" { + special_use = \All + } + + mailbox Virtual/Flagged { + special_use = \Flagged + } +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/20-imap.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/20-imap.conf.j2 new file mode 100644 index 0000000..ae67bae --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/20-imap.conf.j2 @@ -0,0 +1,3 @@ +protocol imap { + mail_plugins = $mail_plugins imap_quota imap_sieve +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/20-lmtp.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/20-lmtp.conf.j2 new file mode 100644 index 0000000..2619ce5 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/20-lmtp.conf.j2 @@ -0,0 +1,3 @@ +protocol lmtp { + mail_plugins = $mail_plugins sieve +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/20-managesieve.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/20-managesieve.conf.j2 new file mode 100644 index 0000000..f4adea9 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/20-managesieve.conf.j2 @@ -0,0 +1,11 @@ +protocols = $protocols sieve + +service managesieve-login { + inet_listener sieve { + port = 4190 + } + + inet_listener sieve_deprecated { + port = 0 + } +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/90-fts.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/90-fts.conf.j2 new file mode 100644 index 0000000..dbe2102 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/90-fts.conf.j2 @@ -0,0 +1,6 @@ +plugin { + fts_autoindex = yes + fts = solr + fts_solr = url=http://localhost:{{ dovecot_solr_port }}/solr/dovecot/ + fts_tika = http://localhost:{{ dovecot_tika_port }}/tika/ +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/90-quota.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/90-quota.conf.j2 new file mode 100644 index 0000000..e1d4449 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/90-quota.conf.j2 @@ -0,0 +1,34 @@ +plugin { + quota = count:User quota + quota_vsizes = yes + quota_rule = *:storage={{ dovecot_default_user_quota }} + quota_grace = {{ dovecot_quota_grace_percent }}%% + + quota_max_mail_size = {{ dovecot_max_mail_size }} + + quota_status_success = DUNNO + quota_status_nouser = DUNNO + quota_status_overquota = "552 5.2.2 Mailbox is full" + + {% for percent in dovecot_quota_warning_percent | sort(reverse=True) %} + quota_warning{% if not loop.first %}{{ loop.index }}{% endif %} = storage={{ percent }}%% quota-warning {{ percent }} %u + {% endfor %} +} + +service quota-warning { + executable = script {{ dovecot_quota_warning_script }} + user = {{ dovecot_vmail_user }} + unix_listener quota-warning { + user = dovecot + group = {{ dovecot_vmail_user }} + mode = 0660 + } +} + +service quota-status { + executable = quota-status -p postfix + inet_listener { + port = {{ dovecot_quota_status_port }} + } + client_limit = 5 +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/90-sieve-extprograms.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/90-sieve-extprograms.conf.j2 new file mode 100644 index 0000000..bab3d4f --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/90-sieve-extprograms.conf.j2 @@ -0,0 +1,5 @@ +plugin { + sieve_pipe_bin_dir = {{ dovecot_sieve_pipe_bin_dir }} + sieve_filter_bin_dir = /usr/lib/dovecot/sieve-filter + sieve_execute_bin_dir = /usr/lib/dovecot/sieve-execute +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/90-sieve.conf.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/90-sieve.conf.j2 new file mode 100644 index 0000000..51ec533 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/90-sieve.conf.j2 @@ -0,0 +1,30 @@ +plugin { + sieve = file:~/sieve;active=~/.dovecot.sieve + + sieve_before = {{ dovecot_sieve_before_dir }} + + sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute + + sieve_plugins = sieve_extprograms sieve_imapsieve + + sieve_quota_max_scripts = 10 + sieve_quota_max_storage = 2M + + sieve_user_email = %Ln@{{ dovecot_default_domain }} + + # The default value for this is "sender", but that will totally break SPF + sieve_redirect_envelope_from = orig_recipient + + # From elsewhere to Junk folder + imapsieve_mailbox1_name = Junk + imapsieve_mailbox1_causes = COPY + imapsieve_mailbox1_before = file:{{ dovecot_sieve_dir }}/report-spam.sieve + + # From Junk folder to elsewhere + imapsieve_mailbox2_name = * + imapsieve_mailbox2_from = Junk + imapsieve_mailbox2_causes = COPY + imapsieve_mailbox2_before = file:{{ dovecot_sieve_dir }}/report-ham.sieve + + sieve_global_extensions = +vnd.dovecot.pipe +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/auth-ldap.conf.ext.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/auth-ldap.conf.ext.j2 new file mode 100644 index 0000000..7b5ab0e --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/auth-ldap.conf.ext.j2 @@ -0,0 +1,4 @@ +userdb { + driver = ldap + args = /etc/dovecot/dovecot-ldap.conf.ext +} diff --git a/roles/dovecot/templates/etc/dovecot/conf.d/auth-system.conf.ext.j2 b/roles/dovecot/templates/etc/dovecot/conf.d/auth-system.conf.ext.j2 new file mode 100644 index 0000000..a53dd53 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/conf.d/auth-system.conf.ext.j2 @@ -0,0 +1,3 @@ +passdb { + driver = pam +} diff --git a/roles/dovecot/templates/etc/dovecot/dovecot-ldap.conf.ext.j2 b/roles/dovecot/templates/etc/dovecot/dovecot-ldap.conf.ext.j2 new file mode 100644 index 0000000..3f03c82 --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/dovecot-ldap.conf.ext.j2 @@ -0,0 +1,16 @@ +hosts = {{ freeipa_hosts | join(' ') }} + +sasl_bind = yes +sasl_mech = gssapi +sasl_realm = {{ freeipa_realm }} + +base = {{ freeipa_user_basedn }} + +user_filter = (&(uid=%Ln)(memberof=cn={{ dovecot_access_group }},{{ freeipa_group_basedn }})) +user_attrs= \ + =uid={{ dovecot_vmail_user }}, \ + =gid={{ dovecot_vmail_user }}, \ + =home={{ dovecot_vmail_dir }}/%{ldap:uid} + +iterate_attrs = uid=user +iterate_filter = (memberof=cn={{ dovecot_access_group }},{{ freeipa_group_basedn }}) diff --git a/roles/dovecot/templates/etc/dovecot/dovecot.conf.j2 b/roles/dovecot/templates/etc/dovecot/dovecot.conf.j2 new file mode 100644 index 0000000..bfc16bf --- /dev/null +++ b/roles/dovecot/templates/etc/dovecot/dovecot.conf.j2 @@ -0,0 +1,5 @@ +protocols = imap lmtp + +import_environment = $import_environment GSS_USE_PROXY=yes + +!include conf.d/*.conf diff --git a/roles/dovecot/templates/usr/lib/dovecot/sieve-pipe/report-ham.sh.j2 b/roles/dovecot/templates/usr/lib/dovecot/sieve-pipe/report-ham.sh.j2 new file mode 100644 index 0000000..fbce0bc --- /dev/null +++ b/roles/dovecot/templates/usr/lib/dovecot/sieve-pipe/report-ham.sh.j2 @@ -0,0 +1,7 @@ +#!/bin/bash + +exec /usr/bin/rspamc \ + --hostname={{ dovecot_rspamd_host | quote }} \ + --password={{ dovecot_rspamd_password | quote }} \ + --key={{ dovecot_rspamd_pubkey | quote }} \ + learn_ham diff --git a/roles/dovecot/templates/usr/lib/dovecot/sieve-pipe/report-spam.sh.j2 b/roles/dovecot/templates/usr/lib/dovecot/sieve-pipe/report-spam.sh.j2 new file mode 100644 index 0000000..393c5ec --- /dev/null +++ b/roles/dovecot/templates/usr/lib/dovecot/sieve-pipe/report-spam.sh.j2 @@ -0,0 +1,7 @@ +#!/bin/bash + +exec /usr/bin/rspamc \ + --hostname={{ dovecot_rspamd_host | quote }} \ + --password={{ dovecot_rspamd_password | quote }} \ + --key={{ dovecot_rspamd_pubkey | quote }} \ + learn_spam diff --git a/roles/dovecot/templates/usr/local/bin/dovecot-archive.sh.j2 b/roles/dovecot/templates/usr/local/bin/dovecot-archive.sh.j2 new file mode 100644 index 0000000..8f34b6a --- /dev/null +++ b/roles/dovecot/templates/usr/local/bin/dovecot-archive.sh.j2 @@ -0,0 +1,19 @@ +#!/bin/bash + +set -Eeu -o pipefail + +VMAIL_USER={{ dovecot_vmail_user | quote }} +{% raw %} +TMPDIR=$(mktemp -d .dovecot-XXXXXX) +trap 'rm -rf -- "$TMPDIR"' EXIT + +chown "$VMAIL_USER" "$TMPDIR" + +doveadm user '*' | xargs -r -I{} doveadm -o plugin/quota= backup -n inbox -f -u {} "mdbox:${TMPDIR}/{}/mdbox:LAYOUT=fs" + +TIMESTAMP=$(date +%Y%m%d%H%M%S) + +tar czf "mailboxes-${TIMESTAMP}.tar.gz" \ + --transform "s|^\.|mailboxes-${TIMESTAMP}|" \ + -C "$TMPDIR" . +{% endraw %} diff --git a/roles/dovecot/templates/usr/local/bin/dovecot-quota-warning.sh.j2 b/roles/dovecot/templates/usr/local/bin/dovecot-quota-warning.sh.j2 new file mode 100644 index 0000000..5ffe4b8 --- /dev/null +++ b/roles/dovecot/templates/usr/local/bin/dovecot-quota-warning.sh.j2 @@ -0,0 +1,19 @@ +#!/bin/bash + +set -Eeu -o pipefail + +PERCENT=$1 +USER=$2 + +cat << EOF | /usr/libexec/dovecot/dovecot-lda -d "$USER" -o "plugin/quota=count:User quota:noenforcing" +From: postmaster@{{ dovecot_default_domain }} +Subject: Mailbox quota warning + +This is an automatically generated message. + +Your mailbox is now ${PERCENT}% full. + +When your mailbox exceeds its quota, you will no longer receive new mail. + +Please delete some messages to free up space. +EOF diff --git a/roles/dovecot/templates/var/lib/solr/dovecot/conf/solrconfig.xml.j2 b/roles/dovecot/templates/var/lib/solr/dovecot/conf/solrconfig.xml.j2 new file mode 100644 index 0000000..af29a84 --- /dev/null +++ b/roles/dovecot/templates/var/lib/solr/dovecot/conf/solrconfig.xml.j2 @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<config> + <luceneMatchVersion>{{ solr_lucene_version }}</luceneMatchVersion> + + <lib dir="${solr.install.dir:../../../..}/contrib/extraction/lib" regex=".*\.jar" /> + <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-cell-\d.*\.jar" /> + + <lib dir="${solr.install.dir:../../../..}/contrib/clustering/lib/" regex=".*\.jar" /> + <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-clustering-\d.*\.jar" /> + + <lib dir="${solr.install.dir:../../../..}/contrib/langid/lib/" regex=".*\.jar" /> + <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-langid-\d.*\.jar" /> + + <lib dir="${solr.install.dir:../../../..}/contrib/velocity/lib" regex=".*\.jar" /> + <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-velocity-\d.*\.jar" /> + + <dataDir>${solr.data.dir:}</dataDir> + + <updateHandler class="solr.DirectUpdateHandler2"> + + <updateLog> + <str name="dir">${solr.ulog.dir:}</str> + <int name="numVersionBuckets">${solr.ulog.numVersionBuckets:65536}</int> + </updateLog> + + <autoCommit> + <maxTime>${solr.autoCommit.maxTime:15000}</maxTime> + <openSearcher>false</openSearcher> + </autoCommit> + + <autoSoftCommit> + <maxTime>${solr.autoSoftCommit.maxTime:-1}</maxTime> + </autoSoftCommit> + + </updateHandler> + + <query> + <filterCache class="solr.CaffeineCache" + size="512" + initialSize="512" + autowarmCount="0"/> + + <queryResultCache class="solr.CaffeineCache" + size="512" + initialSize="512" + autowarmCount="0"/> + + <documentCache class="solr.CaffeineCache" + size="512" + initialSize="512" + autowarmCount="0"/> + + <cache name="perSegFilter" + class="solr.search.CaffeineCache" + size="10" + initialSize="0" + autowarmCount="10" + regenerator="solr.NoOpRegenerator" /> + + <enableLazyFieldLoading>true</enableLazyFieldLoading> + + <queryResultWindowSize>20</queryResultWindowSize> + + <queryResultMaxDocsCached>200</queryResultMaxDocsCached> + + <useColdSearcher>false</useColdSearcher> + + </query> + + <requestDispatcher> + <httpCaching never304="true" /> + </requestDispatcher> + + <requestHandler name="/select" class="solr.SearchHandler"> + <lst name="defaults"> + <str name="echoParams">explicit</str> + <int name="rows">10</int> + </lst> + </requestHandler> + + <initParams path="/update/**,/select"> + <lst name="defaults"> + <str name="df">_text_</str> + </lst> + </initParams> + + <queryResponseWriter name="xml" + default="true" + class="solr.XMLResponseWriter" /> +</config> diff --git a/roles/dovecot/vars/main.yml b/roles/dovecot/vars/main.yml new file mode 100644 index 0000000..5069aa5 --- /dev/null +++ b/roles/dovecot/vars/main.yml @@ -0,0 +1,64 @@ +dovecot_packages: + - dovecot + - dovecot-pigeonhole + +dovecot_vmail_user: vmail +dovecot_vmail_dir: /var/vmail + +dovecot_hbac_hostgroup: imap_servers +dovecot_hbac_service: dovecot + +dovecot_certificate_path: /etc/pki/dovecot/certs/dovecot.pem +dovecot_certificate_key_path: /etc/pki/dovecot/private/dovecot.key +dovecot_dhparams_path: /etc/pki/dovecot/dhparams-dovecot.pem +dovecot_ssl_cipher_list: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + +dovecot_quota_warning_script: /usr/local/bin/dovecot-quota-warning.sh +dovecot_archive_script: /usr/local/bin/dovecot-archive.sh + +dovecot_keytab: /var/lib/gssproxy/clients/dovecot.keytab + +dovecot_sieve_dir: /etc/dovecot/sieve +dovecot_sieve_before_dir: /etc/dovecot/sieve.before.d +dovecot_sieve_pipe_bin_dir: /usr/lib/dovecot/sieve-pipe + +dovecot_solr_schema_path: /usr/share/doc/dovecot/solr-schema-7.7.0.xml +dovecot_solr_config_path: /usr/share/doc/dovecot/solr-config-7.7.0.xml + +dovecot_selinux_policy_te: | + require { + type autofs_t; + type dovecot_t; + type dovecot_auth_t; + type dovecot_auth_exec_t; + type dovecot_deliver_exec_t; + type gssd_t; + type gssproxy_t; + type gssproxy_var_lib_t; + class dir search; + class sock_file write; + class unix_stream_socket connectto; + class process noatsecure; + class file { read execute open getattr execute_no_trans map }; + class dir search; + class key { read write }; + } + + ### The following rules are needed for dovecot to access gssproxy: + #============= dovecot_auth_t ============== + allow dovecot_auth_t gssproxy_t:unix_stream_socket connectto; + allow dovecot_auth_t gssproxy_var_lib_t:dir search; + allow dovecot_auth_t gssproxy_var_lib_t:sock_file write; + allow dovecot_auth_t autofs_t:dir search; + allow dovecot_auth_t gssd_t:key { read write }; + + #============= dovecot_t ============== + allow dovecot_t dovecot_auth_t:process noatsecure; + allow dovecot_t dovecot_deliver_exec_t:file { read execute open getattr execute_no_trans }; + + #============= gssproxy_t ============== + allow gssproxy_t dovecot_auth_exec_t:file getattr; + + ### The following rules are needed for the delivery process to exec quota warning scripts: + #============= dovecot_t ============== + allow dovecot_t dovecot_deliver_exec_t:file { read execute open getattr execute_no_trans map }; |