From 1204730924436ef9e1c7c49c9557837f9a5ed0e8 Mon Sep 17 00:00:00 2001 From: clarkzjw Date: Wed, 8 Feb 2023 00:40:09 -0800 Subject: fork https://github.com/mattsta/mailweb --- .../dovecot/files/dovecot/authdb.sqlite3.empty | Bin 0 -> 2048 bytes .../roles/dovecot/files/dovecot/conf.d/10-acl.conf | 15 +++ .../dovecot/files/dovecot/conf.d/10-auth.conf | 46 +++++++ .../dovecot/files/dovecot/conf.d/10-mail.conf | 28 ++++ .../dovecot/files/dovecot/conf.d/10-master.conf | 82 +++++++++++ .../roles/dovecot/files/dovecot/conf.d/15-lda.conf | 7 + .../dovecot/files/dovecot/conf.d/15-mailboxes.conf | 24 ++++ .../dovecot/files/dovecot/conf.d/20-imap.conf | 10 ++ .../dovecot/files/dovecot/conf.d/20-lmtp.conf | 5 + .../dovecot/files/dovecot/conf.d/90-imapsieve.conf | 18 +++ .../dovecot/files/dovecot/conf.d/90-sieve.conf | 34 +++++ .../dovecot/files/dovecot/dovecot-sql.conf.ext | 150 +++++++++++++++++++++ ansible/roles/dovecot/files/dovecot/dovecot.conf | 19 +++ .../files/dovecot/sieve-before.d/10-rspamd.sieve | 5 + .../dovecot/files/dovecot/sieve/report-ham.sieve | 23 ++++ .../dovecot/files/dovecot/sieve/report-spam.sieve | 7 + .../roles/dovecot/files/dovecot/sieve/train-ham.sh | 1 + .../dovecot/files/dovecot/sieve/train-spam.sh | 1 + ansible/roles/dovecot/handlers/main.yml | 31 +++++ ansible/roles/dovecot/tasks/main.yml | 110 +++++++++++++++ .../templates/dovecot/conf.d/10-ssl.conf.j2 | 22 +++ 21 files changed, 638 insertions(+) create mode 100644 ansible/roles/dovecot/files/dovecot/authdb.sqlite3.empty create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/10-acl.conf create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/10-auth.conf create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/10-mail.conf create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/10-master.conf create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/15-lda.conf create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/15-mailboxes.conf create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/20-imap.conf create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/20-lmtp.conf create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/90-imapsieve.conf create mode 100644 ansible/roles/dovecot/files/dovecot/conf.d/90-sieve.conf create mode 100644 ansible/roles/dovecot/files/dovecot/dovecot-sql.conf.ext create mode 100644 ansible/roles/dovecot/files/dovecot/dovecot.conf create mode 100644 ansible/roles/dovecot/files/dovecot/sieve-before.d/10-rspamd.sieve create mode 100644 ansible/roles/dovecot/files/dovecot/sieve/report-ham.sieve create mode 100644 ansible/roles/dovecot/files/dovecot/sieve/report-spam.sieve create mode 100755 ansible/roles/dovecot/files/dovecot/sieve/train-ham.sh create mode 100755 ansible/roles/dovecot/files/dovecot/sieve/train-spam.sh create mode 100644 ansible/roles/dovecot/handlers/main.yml create mode 100644 ansible/roles/dovecot/tasks/main.yml create mode 100644 ansible/roles/dovecot/templates/dovecot/conf.d/10-ssl.conf.j2 (limited to 'ansible/roles/dovecot') diff --git a/ansible/roles/dovecot/files/dovecot/authdb.sqlite3.empty b/ansible/roles/dovecot/files/dovecot/authdb.sqlite3.empty new file mode 100644 index 0000000..f1e0330 Binary files /dev/null and b/ansible/roles/dovecot/files/dovecot/authdb.sqlite3.empty differ diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/10-acl.conf b/ansible/roles/dovecot/files/dovecot/conf.d/10-acl.conf new file mode 100644 index 0000000..f9fa335 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/10-acl.conf @@ -0,0 +1,15 @@ +mail_plugins = $mail_plugins acl + +protocol imap { + mail_plugins = $mail_plugins imap_acl +} + +plugin { + acl_defaults_from_inbox = yes +} + +# Should saving a mail to a nonexistent mailbox automatically create it? +lda_mailbox_autocreate = yes + +# Should automatically created mailboxes be also automatically subscribed? +lda_mailbox_autosubscribe = yes diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/10-auth.conf b/ansible/roles/dovecot/files/dovecot/conf.d/10-auth.conf new file mode 100644 index 0000000..4e23fa8 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/10-auth.conf @@ -0,0 +1,46 @@ +# cache all authentication results for one hour +#auth_cache_size = 10M +#auth_cache_ttl = 1 hour +#auth_cache_negative_ttl = 1 hour + +# Don't cache password details, otherwise password changes require a server HUP +# before the server will re-query the password data source. +auth_cache_size = 0 + +# only use plain username/password auth - OK since everything is over TLS +auth_mechanisms = plain + +# passdb specifies how users are authenticated - sql here, and +# our sql config specifies the sqlite filename with queries to use +passdb { + driver = sql + args = /etc/dovecot/dovecot-sql.conf.ext +} + +# userdb specifies the location of users' "home" directories - where their +# mail is stored. e.g. /var/mail/vhosts/exmaple.com/user +# %d = domain, %n = user +# We can't use "prefetch" because postfix can't read users from "prefetch" db, +# and we can't use 'static' because the doveadm tool needs to iterate users +# for purging zero refcount deleted mails, so we give a userdb of sql here +# and specify a "get all users" SQL query in the configuration file. +userdb { + driver = sql + args = /etc/dovecot/dovecot-sql.conf.ext +} + +# UNIX socket path to master authentication server to find users. +# This is used by imap (for shared users) and lda. +auth_socket_path = /var/run/dovecot/auth-userdb + +# Respect /etc/hosts.deny (populated by fail2ban) +# You can use /etc/hosts.allow to countermand fail2ban decisions. +login_access_sockets = tcpwrap + +service tcpwrap { + unix_listener login/tcpwrap { + group = $default_login_user + mode = 0600 + user = $default_login_user + } +} diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/10-mail.conf b/ansible/roles/dovecot/files/dovecot/conf.d/10-mail.conf new file mode 100644 index 0000000..6c03965 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/10-mail.conf @@ -0,0 +1,28 @@ +# default home directory location for all users +mail_home = /var/mail/vhosts/%d/%n + +# directory to store mail. The tilda makes it relative to the *dovecot* +# virtual home directory. +# +# I use mdbox - this is Dovecot's own high-performance mail store format. +# There are other slower, more "traditional" formats you can choose from. +# Read about them here: https://wiki2.dovecot.org/MailboxFormat +mail_location = mdbox:~/mdbox + +# nothing fancy - just a standard default namespace with '/' as the +# hierarchy separator +namespace inbox { + separator = / + inbox = yes +} + +# set this to the group that owns your vmail directory. +mail_privileged_group = vmail + +# these lines enable attachment deduplication. Attachments must be somewhat +# large (64k) to store them separately from the mail store. +mail_attachment_dir = /var/mail/attachments +mail_attachment_min_size = 64k + +# we'll uncomment this after we set up Solr in the following section: +# mail_plugins = $mail_plugins fts fts_solr diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/10-master.conf b/ansible/roles/dovecot/files/dovecot/conf.d/10-master.conf new file mode 100644 index 0000000..f99d0f4 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/10-master.conf @@ -0,0 +1,82 @@ +# to improve performance, disable fsync globally - we will enable it for +# some specific services later on +mail_fsync = never + +service imap-login { + # plain-text IMAP should only be accessible from localhost + inet_listener imap { + address = 127.0.0.1, ::1 + } + + # enable high-performance mode, described here: + # https://wiki.dovecot.org/LoginProcess + service_count = 0 + + # set to the number of CPU cores on your server + process_min_avail = 3 + vsz_limit = 1G +} + +# disable POP3 altogether +service pop3-login { + inet_listener pop3 { + port = 0 + } + + inet_listener pop3s { + port = 0 + } +} + +# enable semi-long-lived IMAP processes to improve performance +service imap { + service_count = 256 + # set to the number of CPU cores on your server + process_min_avail = 3 +} + +# expose an LMTP socket for postfix to deliver mail +service lmtp { + unix_listener /var/spool/postfix/private/dovecot-lmtp { + group = postfix + mode = 0600 + user = postfix + } +} + +service auth { + # auth_socket_path points to this userdb socket by default. It's typically + # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have + # full permissions to this socket are able to get a list of all usernames and + # get the results of everyone's userdb lookups. + # + # The default 0666 mode allows anyone to connect to the socket, but the + # userdb lookups will succeed only if the userdb returns an "uid" field that + # matches the caller process's UID. Also if caller's uid or gid matches the + # socket's uid or gid the lookup succeeds. Anything else causes a failure. + # + # To give the caller full permissions to lookup all users, set the mode to + # something else than 0666 and Dovecot lets the kernel enforce the + # permissions (e.g. 0777 allows everyone full permissions). + + # auth for postfix + unix_listener /var/spool/postfix/private/auth { + mode = 0666 + user = postfix + group = postfix + } + + # auth for doveadm tools + unix_listener auth-userdb { + mode = 0666 + user = vmail + group = vmail + } + + client_limit = 840 +} + +# no need to run this as root +service auth-worker { + user = vmail +} diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/15-lda.conf b/ansible/roles/dovecot/files/dovecot/conf.d/15-lda.conf new file mode 100644 index 0000000..32ca50d --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/15-lda.conf @@ -0,0 +1,7 @@ +# configuration for mail delivered by the `dovecot-lda` command. Shouldn't +# be needed since we are using LMTP, but kept for backwards compatibility. +protocol lda { + # use fsync for write-safety - this deals with delivering actual mail + mail_fsync = optimized + mail_plugins = $mail_plugins sieve +} diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/15-mailboxes.conf b/ansible/roles/dovecot/files/dovecot/conf.d/15-mailboxes.conf new file mode 100644 index 0000000..8674b0b --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/15-mailboxes.conf @@ -0,0 +1,24 @@ +# define any special IMAP folders here. You can force them to be created or +# created+subscribed automatically used the `auto` option. +namespace inbox { + mailbox Drafts { + auto = subscribe + special_use = \Drafts + } + mailbox Junk { + auto = create + special_use = \Junk + } + mailbox Trash { + auto = create + special_use = \Trash + } + mailbox Archive { + auto = subscribe + special_use = \Archive + } + mailbox Sent { + auto = subscribe + special_use = \Sent + } +} diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/20-imap.conf b/ansible/roles/dovecot/files/dovecot/conf.d/20-imap.conf new file mode 100644 index 0000000..7b32396 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/20-imap.conf @@ -0,0 +1,10 @@ +# Use a longer IDLE interval to reduce network chatter and save battery +# life. Max is 30 minutes. +imap_idle_notify_interval = 29 mins + +protocol imap { + # max IMAP connections per IP address + mail_max_userip_connections = 50 + # imap_sieve will be used for spam training by rspamd + mail_plugins = $mail_plugins imap_sieve +} diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/20-lmtp.conf b/ansible/roles/dovecot/files/dovecot/conf.d/20-lmtp.conf new file mode 100644 index 0000000..a51ee42 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/20-lmtp.conf @@ -0,0 +1,5 @@ +protocol lmtp { + # use fsync for write-safety - this deals with delivering actual mail + mail_fsync = optimized + mail_plugins = $mail_plugins sieve +} diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/90-imapsieve.conf b/ansible/roles/dovecot/files/dovecot/conf.d/90-imapsieve.conf new file mode 100644 index 0000000..26987b1 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/90-imapsieve.conf @@ -0,0 +1,18 @@ +plugin { + sieve_plugins = sieve_imapsieve sieve_extprograms + + # From elsewhere to Junk folder + imapsieve_mailbox1_name = Junk + imapsieve_mailbox1_causes = COPY + imapsieve_mailbox1_before = file:/etc/dovecot/sieve/report-spam.sieve + + # From Junk folder to elsewhere + imapsieve_mailbox2_name = * + imapsieve_mailbox2_from = Junk + imapsieve_mailbox2_causes = COPY + imapsieve_mailbox2_before = file:/etc/dovecot/sieve/report-ham.sieve + + sieve_pipe_bin_dir = /etc/dovecot/sieve + + sieve_global_extensions = +vnd.dovecot.pipe +} diff --git a/ansible/roles/dovecot/files/dovecot/conf.d/90-sieve.conf b/ansible/roles/dovecot/files/dovecot/conf.d/90-sieve.conf new file mode 100644 index 0000000..9a753bf --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/conf.d/90-sieve.conf @@ -0,0 +1,34 @@ +plugin { + # 'active' is a symlink to one sieve source script inside directory at 'file' + sieve = file:~/sieve;active=~/.dovecot.sieve + + # directory of global sieve scripts to run before and after processing ALL + # incoming mail + sieve_before = /etc/dovecot/sieve-before.d + sieve_after = /etc/dovecot/sieve-after.d + + # make sieve aware of user+tag@domain.tld aliases + recipient_delimiter = + + + + # no limits on script size or actions + sieve_quota_max_storage = 0 + sieve_max_script_size = 0 + sieve_max_actions = 0 + + sieve_extensions = +spamtest +spamtestplus + + sieve_spamtest_status_header = X-Spam-Score + sieve_spamtest_status_type = strlen + + # X-Spamd-Bar: +++++++++ + sieve_spamtest_max_value = 9 + + + # X-Spamd-Result: default: False [9.19 / 15.00]; + # (regex not fixed to capture the above) + #sieve_spamtest_status_type = score + #sieve_spamtest_status_header = \ + # X-Spamd-Result: [[:alnum:]]+, score=(-?[[:digit:]]+\.[[:digit:]]) + #sieve_spamtest_max_value = 5.0 +} diff --git a/ansible/roles/dovecot/files/dovecot/dovecot-sql.conf.ext b/ansible/roles/dovecot/files/dovecot/dovecot-sql.conf.ext new file mode 100644 index 0000000..f248243 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/dovecot-sql.conf.ext @@ -0,0 +1,150 @@ +# This file is commonly accessed via passdb {} or userdb {} section in +# conf.d/auth-sql.conf.ext + +# This file is opened as root, so it should be owned by root and mode 0600. +# +# http://wiki2.dovecot.org/AuthDatabase/SQL +# +# For the sql passdb module, you'll need a database with a table that +# contains fields for at least the username and password. If you want to +# use the user@domain syntax, you might want to have a separate domain +# field as well. +# +# If your users all have the same uig/gid, and have predictable home +# directories, you can use the static userdb module to generate the home +# dir based on the username and domain. In this case, you won't need fields +# for home, uid, or gid in the database. +# +# If you prefer to use the sql userdb module, you'll want to add fields +# for home, uid, and gid. Here is an example table: +# +# CREATE TABLE users ( +# username VARCHAR(128) NOT NULL, +# domain VARCHAR(128) NOT NULL, +# password VARCHAR(64) NOT NULL, +# home VARCHAR(255) NOT NULL, +# uid INTEGER NOT NULL, +# gid INTEGER NOT NULL, +# active CHAR(1) DEFAULT 'Y' NOT NULL +# ); + +# Database driver: mysql, pgsql, sqlite +driver = sqlite + +# Database connection string. This is driver-specific setting. +# +# HA / round-robin load-balancing is supported by giving multiple host +# settings, like: host=sql1.host.org host=sql2.host.org +# +# pgsql: +# For available options, see the PostgreSQL documention for the +# PQconnectdb function of libpq. +# Use maxconns=n (default 5) to change how many connections Dovecot can +# create to pgsql. +# +# mysql: +# Basic options emulate PostgreSQL option names: +# host, port, user, password, dbname +# +# But also adds some new settings: +# client_flags - See MySQL manual +# connect_timeout - Connect timeout in seconds (default: 5) +# read_timeout - Read timeout in seconds (default: 30) +# write_timeout - Write timeout in seconds (default: 30) +# ssl_ca, ssl_ca_path - Set either one or both to enable SSL +# ssl_cert, ssl_key - For sending client-side certificates to server +# ssl_cipher - Set minimum allowed cipher security (default: HIGH) +# ssl_verify_server_cert - Verify that the name in the server SSL certificate +# matches the host (default: no) +# option_file - Read options from the given file instead of +# the default my.cnf location +# option_group - Read options from the given group (default: client) +# +# You can connect to UNIX sockets by using host: host=/var/run/mysql.sock +# Note that currently you can't use spaces in parameters. +# +# sqlite: +# The path to the database file. +# +# Examples: +# connect = host=192.168.1.1 dbname=users +# connect = host=sql.example.com dbname=virtual user=virtual password=blarg +# connect = /etc/dovecot/authdb.sqlite +# +connect = /etc/dovecot/authdb.sqlite + +# Default password scheme. +# +# List of supported schemes is in +# http://wiki2.dovecot.org/Authentication/PasswordSchemes +# +#default_pass_scheme = SHA512-CRYPT + +# passdb query to retrieve the password. It can return fields: +# password - The user's password. This field must be returned. +# user - user@domain from the database. Needed with case-insensitive lookups. +# username and domain - An alternative way to represent the "user" field. +# +# The "user" field is often necessary with case-insensitive lookups to avoid +# e.g. "name" and "nAme" logins creating two different mail directories. If +# your user and domain names are in separate fields, you can return "username" +# and "domain" fields instead of "user". +# +# The query can also return other fields which have a special meaning, see +# http://wiki2.dovecot.org/PasswordDatabase/ExtraFields +# +# Commonly used available substitutions (see http://wiki2.dovecot.org/Variables +# for full list): +# %u = entire user@domain +# %n = user part of user@domain +# %d = domain part of user@domain +# +# Note that these can be used only as input to SQL query. If the query outputs +# any of these substitutions, they're not touched. Otherwise it would be +# difficult to have eg. usernames containing '%' characters. +# +# Example: +# password_query = SELECT userid AS user, pw AS password \ +# FROM users WHERE userid = '%u' AND active = 'Y' +# +password_query = \ + SELECT '%u' AS username, domain, password \ + FROM users WHERE userid = '%n' AND domain = '%d' + +# You can update (or modify this a bit to insert) user passwords in a shell with: +# sqlite3 authdb.sqlite "update users set password='$(doveadm pw -s SHA512-CRYPT -r 1856250)' where userid='USERNAME' and domain = 'DOMAIN';" + + +# userdb query to retrieve the user information. It can return fields: +# uid - System UID (overrides mail_uid setting) +# gid - System GID (overrides mail_gid setting) +# home - Home directory +# mail - Mail location (overrides mail_location setting) +# +# None of these are strictly required. If you use a single UID and GID, and +# home or mail directory fits to a template string, you could use userdb static +# instead. For a list of all fields that can be returned, see +# http://wiki2.dovecot.org/UserDatabase/ExtraFields +# +# Examples: +# user_query = SELECT home, uid, gid FROM users WHERE userid = '%u' +# user_query = SELECT dir AS home, user AS uid, group AS gid FROM users where userid = '%u' +# user_query = SELECT home, 501 AS uid, 501 AS gid FROM users WHERE userid = '%u' +# +user_query = \ + SELECT "/var/mail/vhosts/" || '%d' || '/' || '%n' AS home, 145 as uid, 145 as gid + +# If you wish to avoid two SQL lookups (passdb + userdb), you can use +# userdb prefetch instead of userdb sql in dovecot.conf. In that case you'll +# also have to return userdb fields in password_query prefixed with "userdb_" +# string. For example: +password_query = \ + SELECT '%u' AS user, password, \ + "/var/mail/vhosts/" || '%d' || '/' || '%n' AS userdb_home, 145 AS userdb_uid, 145 AS userdb_gid \ + FROM users WHERE userid = '%n' AND domain = '%d' + +# Query to get a list of all usernames. +# This iteration is used for things like globally purging zero refcount emails +# for all users, but to get all users, we have to iterate the user storage, +# hence this iterator query is required. +iterate_query = SELECT userid AS user FROM users diff --git a/ansible/roles/dovecot/files/dovecot/dovecot.conf b/ansible/roles/dovecot/files/dovecot/dovecot.conf new file mode 100644 index 0000000..4304fa6 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/dovecot.conf @@ -0,0 +1,19 @@ +# IMAP for remote access, LMTP for local delivery +protocols = imap lmtp + +# set these to the uid of your `vmail` user +first_valid_uid = 145 +last_valid_uid = 145 + +#mail_debug = yes +##auth_verbose = yes +##auth_debug = yes +##auth_debug_passwords = yes +##auth_verbose_passwords = yes + + +mail_uid = vmail +mail_gid = vmail + +!include conf.d/*.conf +!include_try local.conf diff --git a/ansible/roles/dovecot/files/dovecot/sieve-before.d/10-rspamd.sieve b/ansible/roles/dovecot/files/dovecot/sieve-before.d/10-rspamd.sieve new file mode 100644 index 0000000..7931a71 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/sieve-before.d/10-rspamd.sieve @@ -0,0 +1,5 @@ +require ["fileinto"]; + +if header :is "X-Spam" "Yes" { + fileinto "Junk"; +} diff --git a/ansible/roles/dovecot/files/dovecot/sieve/report-ham.sieve b/ansible/roles/dovecot/files/dovecot/sieve/report-ham.sieve new file mode 100644 index 0000000..2ad40aa --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/sieve/report-ham.sieve @@ -0,0 +1,23 @@ +require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; + +if environment :matches "imap.mailbox" "*" { + set "mailbox" "${1}"; +} + +# This line is important because when we delete from Junk/Spam, +# messages get moved to Trash, which tirggers the "message moved out of +# spam" script (this script) which—usually!—trains the originally classified +# Spam as not-spam. +# BUT, this is just a delete! If we train our spam as not-spam on delete, that +# defeats our goals. +# In short, this always gets run on a message being moved out of Spam, but if +# the target mailbox is Trash, just don't run the trainer this time. +if string "${mailbox}" "Trash" { + stop; +} + +if environment :matches "imap.email" "*" { + set "email" "${1}"; +} + +pipe :copy "train-ham.sh" [ "${email}" ]; diff --git a/ansible/roles/dovecot/files/dovecot/sieve/report-spam.sieve b/ansible/roles/dovecot/files/dovecot/sieve/report-spam.sieve new file mode 100644 index 0000000..4ed95d7 --- /dev/null +++ b/ansible/roles/dovecot/files/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 "train-spam.sh" [ "${email}" ]; diff --git a/ansible/roles/dovecot/files/dovecot/sieve/train-ham.sh b/ansible/roles/dovecot/files/dovecot/sieve/train-ham.sh new file mode 100755 index 0000000..b0f30fd --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/sieve/train-ham.sh @@ -0,0 +1 @@ +exec /usr/bin/rspamc --connect localhost:11334 learn_ham diff --git a/ansible/roles/dovecot/files/dovecot/sieve/train-spam.sh b/ansible/roles/dovecot/files/dovecot/sieve/train-spam.sh new file mode 100755 index 0000000..bf1b920 --- /dev/null +++ b/ansible/roles/dovecot/files/dovecot/sieve/train-spam.sh @@ -0,0 +1 @@ +exec /usr/bin/rspamc --connect localhost:11334 learn_spam diff --git a/ansible/roles/dovecot/handlers/main.yml b/ansible/roles/dovecot/handlers/main.yml new file mode 100644 index 0000000..dda7930 --- /dev/null +++ b/ansible/roles/dovecot/handlers/main.yml @@ -0,0 +1,31 @@ +--- +- name: restart dovecot + service: + name: dovecot + state: restarted + +- name: reload dovecot + service: + name: dovecot + state: reloaded + +# We intentionally don't have a "creates:" guard on the resieve handlers +# because if they get called we need to re-run them on any changes of +# the underlying script itself regardless whether the result .svbin +# already exists or not. +- name: resieve spam + command: sievec report-spam.sieve + args: + chdir: /etc/dovecot/sieve + +- name: resieve ham + command: sievec report-ham.sieve + args: + chdir: /etc/dovecot/sieve + +- name: resieve spam mover + command: sievec 10-rspamd.sieve + args: + chdir: /etc/dovecot/sieve-before.d + creates: 10-rspamd.svbin + diff --git a/ansible/roles/dovecot/tasks/main.yml b/ansible/roles/dovecot/tasks/main.yml new file mode 100644 index 0000000..6e097c1 --- /dev/null +++ b/ansible/roles/dovecot/tasks/main.yml @@ -0,0 +1,110 @@ +--- +# dovecot install and configuration +- name: install dovecot + apt: + state: latest + pkg: + - dovecot-imapd + - dovecot-lmtpd + - dovecot-sieve + - dovecot-sqlite + +# Convert existing maildir to mdbox (local on-fs dirs) with: +# dsync -o mail_location=mdbox:herebox mirror maildir:Maildir +# Or, you can pull from a remote site: +# Below, -R means REVERSE backup so PULL messages FROM vorash INTO mdbox, +# otherwise, it's a PUSH backup and mdbox PUSHES to vorash which isn't what we want +# doveadm -o mail_location=mdbox:herebox backup -R ssh -J matt@novus.webdev.bz matt@vorash doveadm dsync-server +- name: create mail spool dirs + file: + path: /var/mail/local + owner: root + group: mail + mode: 0775 + state: directory + +- name: create dovecot virtual mailbox group + group: + name: vmail + gid: 145 + state: present + +- name: create dovecot virtual mailbox and virtual authentication account + user: + name: vmail + uid: 145 + group: vmail + shell: /sbin/nologin + create_home: yes + home: /var/mail/vhosts + state: present + +- name: give dovecot user permission to read private keys + user: + name: dovecot + groups: ssl-cert + append: yes + +# Create new passwords with: +# time doveadm pw -s SHA512-CRYPT -r 1856250 +- name: copy dovecot configs and userdb + copy: + src: dovecot/ + dest: /etc/dovecot/ + mode: preserve + notify: + - resieve spam + - resieve ham + - resieve spam mover + - restart dovecot + +# This permission is important because dovecot has multiple users: +# - dovecot +# - dovenull +# - vmail +# but login processes are run by the 'vmail' user, so 'vmail' must have read +# access to the DB +- name: fix user permissions on authdb + file: + path: /etc/dovecot/authdb.sqlite + owner: vmail + group: vmail + mode: 0600 + +- name: instantiate dovecot SSL template with host vars + template: + src: dovecot/conf.d/10-ssl.conf.j2 + dest: /etc/dovecot/conf.d/10-ssl.conf + notify: + - restart dovecot # NB this could be a reload instead + +# Dovecot mdbox format requires a purge to remove storage space +# allocated to messages that have been fully deleted by users. +# (it's an append-only refcounting system, so when a refcount becomes +# zero on final delete, it needs some cleanup to rewrite the old +# pack files without the deleted emails present anymore.) +- cron: + name: setup cron so dovecot can GC mailboxes + minute: 0 + hour: 3 + user: vmail + job: "doveadm purge -A" + cron_file: dovecot_maint_purge + + +# verify everything is running +- name: verify services are running in dependency order + service: + name: "{{ item }}" + enabled: yes + state: started + loop: + - dovecot + +- name: reload if certs newish + include_role: + name: certreload + vars: + certreload: + notifiers: + - reload dovecot diff --git a/ansible/roles/dovecot/templates/dovecot/conf.d/10-ssl.conf.j2 b/ansible/roles/dovecot/templates/dovecot/conf.d/10-ssl.conf.j2 new file mode 100644 index 0000000..c1654cd --- /dev/null +++ b/ansible/roles/dovecot/templates/dovecot/conf.d/10-ssl.conf.j2 @@ -0,0 +1,22 @@ +# require SSL for all non-localhost connections +ssl = required + +# Config detials at https://wiki.dovecot.org/SSL/DovecotConfiguration +ssl_cert =