Dovecot¶
Inhalt¶
- Dovecot 2.3.21.1
- Dovecot Pigeonhole 0.5.21.1
- IMAPS, LMTP, Sieve/ManageSieve
- virtuelle Mailuser mit PostgreSQL-Anbindung
- Master User und Quota-Warning-Script (FreshPorts)
Einleitung¶
Dieses HowTo beschreibt die Installation und Konfiguration von Dovecot auf FreeBSD 15+.
Dovecot dient hier als Backend für IMAP/IMAPS, LMTP und virtuelle Mailuser. Für Sieve und ManageSieve wird Dovecot Pigeonhole verwendet. Da dieses Setup PostgreSQL für passdb, userdb und weitere SQL-Lookups nutzt, ist auf FreeBSD der pgsql-Flavor von mail/dovecot und mail/dovecot-pigeonhole die richtige Basis. Dovecot dokumentiert SQL ausdrücklich als üblichen Weg für virtuelle Benutzer, und Pigeonhole ergänzt Dovecot um Sieve und ManageSieve. (FreshPorts)
Voraussetzungen¶
Zu den Voraussetzungen für dieses HowTo siehe bitte: Hosting System
Zusätzlich wird vorausgesetzt:
- PostgreSQL ist bereits installiert und erreichbar.
- Der Systembenutzer
vmailmit UID/GID 5000 existiert bereits. - Die TLS-Zertifikate für
mail.example.comexistieren bereits. - Postfix ist für die SASL-/LMTP-Sockets unter
/var/spool/postfix/private/vorbereitet.
Vorbereitungen¶
DNS Records¶
Für dieses HowTo müssen zuvor folgende DNS-Records angelegt werden, sofern sie noch nicht existieren, oder entsprechend geändert werden, sofern sie bereits existieren.
Gruppen / Benutzer / Passwörter¶
Für dieses HowTo müssen keine zusätzlichen Systemgruppen oder Systembenutzer angelegt werden.
Vorausgesetzt wird der bereits vorhandene Systembenutzer vmail mit UID/GID 5000.
Für dieses HowTo wird zusätzlich ein Passwort für den Dovecot-Master-User erzeugt und unter /var/db/passwords/user_dovecot_superuser gespeichert.
Verzeichnisse / Dateien¶
Für dieses HowTo müssen zuvor folgende Verzeichnisse angelegt werden, sofern sie noch nicht existieren, oder entsprechend geändert werden, sofern sie bereits existieren.
Für diese HowTos müssen zuvor folgende Dateien angelegt werden, sofern sie noch nicht existieren, oder entsprechend geändert werden, sofern sie bereits existieren.
Installation¶
Wir installieren mail/dovecot@pgsql und dessen Abhängigkeiten.¶
mkdir -p /var/db/ports/textproc_libexttextcat
cat <<'EOF' > /var/db/ports/textproc_libexttextcat/options
_OPTIONS_READ=libexttextcat-3.4.6
_FILE_COMPLETE_OPTIONS_LIST=DOCS
OPTIONS_FILE_UNSET+=DOCS
EOF
mkdir -p /var/db/ports/mail_dovecot
cat <<'EOF' > /var/db/ports/mail_dovecot/options
_OPTIONS_READ=dovecot-2.3.21.1
_FILE_COMPLETE_OPTIONS_LIST=DOCS EXAMPLES LIBSODIUM LIBUNWIND LIBWRAP LUA LZ4 GSSAPI_BASE GSSAPI_HEIMDAL GSSAPI_MIT GSSAPI_NONE CDB LDAP MYSQL PGSQL SQLITE ICU LUCENE SOLR TEXTCAT
OPTIONS_FILE_UNSET+=DOCS
OPTIONS_FILE_UNSET+=EXAMPLES
OPTIONS_FILE_SET+=LIBSODIUM
OPTIONS_FILE_SET+=LIBUNWIND
OPTIONS_FILE_SET+=LIBWRAP
OPTIONS_FILE_SET+=LUA
OPTIONS_FILE_SET+=LZ4
OPTIONS_FILE_UNSET+=GSSAPI_BASE
OPTIONS_FILE_UNSET+=GSSAPI_HEIMDAL
OPTIONS_FILE_UNSET+=GSSAPI_MIT
OPTIONS_FILE_SET+=GSSAPI_NONE
OPTIONS_FILE_SET+=CDB
OPTIONS_FILE_UNSET+=LDAP
OPTIONS_FILE_SET+=MYSQL
OPTIONS_FILE_SET+=PGSQL
OPTIONS_FILE_UNSET+=SQLITE
OPTIONS_FILE_SET+=ICU
OPTIONS_FILE_UNSET+=LUCENE
OPTIONS_FILE_UNSET+=SOLR
OPTIONS_FILE_SET+=TEXTCAT
EOF
portmaster -w -B -g -U --force-config mail/dovecot@pgsql -n
pw groupmod dovecot -m www
pw groupmod mail -m dovecot
Wir installieren mail/dovecot-pigeonhole@pgsql und dessen Abhängigkeiten.¶
mkdir -p /var/db/ports/mail_dovecot-pigeonhole
cat <<'EOF' > /var/db/ports/mail_dovecot-pigeonhole/options
_OPTIONS_READ=dovecot-pigeonhole-pgsql-0.5.21.1
_FILE_COMPLETE_OPTIONS_LIST=DOCS EXAMPLES LDAP MANAGESIEVE GSSAPI_NONE GSSAPI_BASE GSSAPI_HEIMDAL GSSAPI_MIT
OPTIONS_FILE_UNSET+=DOCS
OPTIONS_FILE_UNSET+=EXAMPLES
OPTIONS_FILE_UNSET+=LDAP
OPTIONS_FILE_SET+=MANAGESIEVE
OPTIONS_FILE_SET+=GSSAPI_NONE
OPTIONS_FILE_UNSET+=GSSAPI_BASE
OPTIONS_FILE_UNSET+=GSSAPI_HEIMDAL
OPTIONS_FILE_UNSET+=GSSAPI_MIT
EOF
portmaster -w -B -g -U --force-config mail/dovecot-pigeonhole@pgsql -n
Die Ports und Flavors passen genau zu diesem Setup: mail/dovecot liefert den rc.d-Dienst dovecot, mail/dovecot-pigeonhole ergänzt die Sieve-/ManageSieve-Funktionen, und beide Ports bieten einen pgsql-Flavor für PostgreSQL-basierte Installationen. (FreshPorts)
Dienst in rc.conf eintragen¶
Der Dienst wird mittels sysrc in der rc.conf eingetragen und dadurch beim Systemstart automatisch gestartet.
Konfiguration¶
Konfigurationsdateien¶
cat <<'EOF' > /usr/local/etc/dovecot/dovecot.conf
#dovecot_config_version = 2.3.21
#dovecot_storage_version = 2.3.21
# -------------------------------------------------------------------------
# FreeBSD System Optimizations
# -------------------------------------------------------------------------
#import_environment {
# TZ = :/etc/localtime
# SSL_CERT_DIR = /etc/ssl/certs
#}
# -------------------------------------------------------------------------
# Authentication Settings
# -------------------------------------------------------------------------
#auth_debug = yes
auth_cache_size = 100M
auth_cache_verify_password_with_worker = yes
auth_master_user_separator = *
auth_mechanisms = plain login
auth_stats = yes
auth_verbose = yes
auth_verbose_passwords = sha1:8
auth_failure_delay = 2 secs
#auth_max_failures = 10
disable_plaintext_auth = yes
# -------------------------------------------------------------------------
# General & Mail Settings
# -------------------------------------------------------------------------
first_valid_gid = 5000
first_valid_uid = 5000
last_valid_gid = 5000
last_valid_uid = 5000
mail_uid = 5000
mail_gid = 5000
hostname = mail.example.com
# Core format settings
deliver_log_format = from=%{from}, envelope_from=%{from_envelope}, envelope_to=%{to_envelope}, msgid=%{msgid}, size=%{size}, delivery_time=%{delivery_time}ms, %$
login_log_format_elements = user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k session=<%{session}>
log_core_filter = category=error
#log_debug = category=auth
verbose_proctitle = yes
# Mailbox and Storage
mail_home = /var/vmail/%d/%n
mail_location = maildir:%Lh/Maildir/:INDEX=%Lh/Maildir/
mail_max_userip_connections = 50
mail_plugins = acl mailbox_alias mail_log notify quota zlib
mail_attribute_dict = file:%h/dovecot-attributes
# Caching & Indexing Optimizations (Crucial for ZFS performance)
#mail_attachment_detection_options = add-flags
mail_always_cache_fields = flags date.save imap.envelope mime.parts imap.bodystructure
mail_cache_fields = flags date.save imap.envelope mime.parts imap.bodystructure
mailbox_list_index = yes
mailbox_list_index_very_dirty_syncs = yes
mail_sort_max_read_count = 100
mail_prefetch_count = 20
# Delivery / LMTP
lda_mailbox_autocreate = yes
lda_mailbox_autosubscribe = yes
lda_original_recipient_header = X-Original-To
lmtp_save_to_detail_mailbox = yes
# Client Workarounds
imap_id_retain = yes
imap_client_workarounds = tb-extra-mailbox-sep
imap_metadata = yes
lmtp_client_workarounds = whitespace-before-path mailbox-for-path
pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
pop3_uidl_format = %08Xu%08Xv
submission_client_workarounds = mailbox-for-path whitespace-before-path
listen = *, [::]
protocols = lmtp imap sieve
quota_full_tempfail = yes
sendmail_path = /usr/local/sbin/sendmail
login_proxy_max_disconnect_delay = 30 secs
#login_trusted_networks = \
# 127.0.0.0/8 [::1]/128 \
# 10.0.0.0/8 [fe80::]/10 \
# __IPADDR4__/32 [__IPADDR6__]/64
mail_server_admin = mailto:admin@example.com
# -------------------------------------------------------------------------
# SSL / TLS Settings (Strict & Modern)
# -------------------------------------------------------------------------
ssl = required
ssl_cert = </usr/local/etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </usr/local/etc/letsencrypt/live/mail.example.com/privkey.pem
ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes
ssl_cipher_suites = TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256
ssl_cipher_list = ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
#ssl_curve_list = P-521:P-384:P-256
#ssl_ocsp = yes
# -------------------------------------------------------------------------
# Databases & Dictionaries
# -------------------------------------------------------------------------
dict {
quota = pgsql:/usr/local/etc/dovecot/dovecot-dict-quota.conf
}
passdb {
driver = passwd-file
args = /usr/local/etc/dovecot/dovecot-master-users
master = yes
skip = authenticated
result_success = continue
result_failure = continue
}
passdb {
driver = passwd-file
args = scheme=ARGON2ID username_format=%{user} /usr/local/etc/dovecot/passwd
default_fields = uid=5000 gid=5000 home=/var/vmail/%d/%n
override_fields = uid=5000 gid=5000 home=/var/vmail/%d/%n
skip = authenticated
result_success = return-ok
result_failure = continue
}
passdb {
driver = sql
args = /usr/local/etc/dovecot/dovecot-pgsql.conf
skip = authenticated
result_success = return-ok
result_failure = return-fail
}
userdb {
driver = prefetch
}
userdb {
driver = passwd-file
args = username_format=%{user} /usr/local/etc/dovecot/passwd
default_fields = uid=5000 gid=5000 home=/var/vmail/%d/%n
override_fields = uid=5000 gid=5000 home=/var/vmail/%d/%n
}
userdb {
driver = sql
args = /usr/local/etc/dovecot/dovecot-pgsql.conf
}
# -------------------------------------------------------------------------
# Namespaces
# -------------------------------------------------------------------------
namespace {
type = private
name = inbox
inbox = yes
separator = /
prefix =
location =
mailbox Archive {
auto = subscribe
special_use = \Archive
}
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Sent {
auto = subscribe
special_use = \Sent
}
mailbox Junk {
auto = subscribe
autoexpunge = 4 weeks
special_use = \Junk
}
mailbox Trash {
auto = subscribe
autoexpunge = 2 weeks
special_use = \Trash
}
}
namespace {
type = shared
name = shared
separator = /
prefix = Shared/%%u/
location = maildir:%%Lh/Maildir/:INDEX=%%Lh/Maildir/Shared/%%Ld/%%Ln
list = children
}
# -------------------------------------------------------------------------
# Plugins & Sieve
# -------------------------------------------------------------------------
plugin {
acl = vfile
mail_log_cached_only = yes
mail_log_events = delete undelete expunge copy mailbox_create mailbox_delete mailbox_rename
mail_log_fields = uid box msgid size from subject flags
quota = dict:storage=2G quota::proxy::quota
quota_grace = 10%%
quota_rule = *:storage=1G
quota_rule2 = Archive:storage=+4G
quota_rule3 = Trash:storage=+100M
quota_rule4 = Junk:ignore
quota_status_nouser = DUNNO
quota_status_overquota = 552 5.2.2 Mailbox is full
quota_status_success = DUNNO
quota_vsizes = yes
quota_warning = storage=85%% quota-warning 85 %u
quota_warning2 = storage=90%% quota-warning 90 %u
quota_warning3 = storage=95%% quota-warning 95 %u
quota_warning4 = storage=100%% quota-warning 100 %u
quota_warning5 = -storage=80%% quota-warning below-80 %u
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_default = /usr/local/etc/dovecot/sieve/default.sieve
sieve_global = /usr/local/etc/dovecot/sieve/global/
sieve_before = /usr/local/etc/dovecot/sieve/before-global.sieve
sieve_max_redirects = 30
# sieve_vacation_send_from_recipient = yes
zlib_save = gz
zlib_save_level = 6
}
# -------------------------------------------------------------------------
# Protocols
# -------------------------------------------------------------------------
protocol lda {
mail_plugins = $mail_plugins sieve
lda_mailbox_autocreate = yes
lda_mailbox_autosubscribe = yes
}
protocol lmtp {
mail_plugins = $mail_plugins sieve
lmtp_save_to_detail_mailbox = yes
recipient_delimiter = +
}
protocol pop3 {
mail_plugins = $mail_plugins sieve
pop3_no_flag_updates = yes
pop3_uidl_format = %08Xu%08Xv
}
protocol imap {
mail_plugins = $mail_plugins imap_quota imap_acl imap_zlib
}
protocol !indexer-worker {
mail_vsize_bg_after_count = 100
}
# -------------------------------------------------------------------------
# Services & Listeners
# -------------------------------------------------------------------------
service auth {
vsz_limit = 2G
unix_listener /var/spool/postfix/private/dovecot-auth {
mode = 0666
user = postfix
group = postfix
}
unix_listener auth-master {
mode = 0666
user = vmail
group = vmail
}
unix_listener auth-userdb {
mode = 0660
user = vmail
group = vmail
}
}
service dict {
unix_listener dict {
mode = 0660
user = vmail
group = vmail
}
}
service imap-login {
process_min_avail = 5
vsz_limit = 1G
inet_listener imap {
port = 0
}
inet_listener imaps {
port = 993
}
}
service lmtp {
executable = lmtp -L
user = vmail
process_min_avail = 5
vsz_limit = 1G
inet_listener lmtp {
address = 127.0.0.1
port = 24
}
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
service pop3-login {
inet_listener pop3 {
port = 0
}
inet_listener pop3s {
port = 0
}
}
service quota-warning {
executable = script /usr/local/bin/dovecot-quota-warning.sh
client_limit = 1
user = vmail
group = vmail
unix_listener quota-warning {
mode = 0660
user = vmail
group = vmail
}
}
service quota-status {
executable = quota-status -p postfix
client_limit = 1
inet_listener {
address = 127.0.0.1
port = 12340
}
}
service stats {
inet_listener {
address = 127.0.0.1
port = 24242
}
unix_listener stats-reader {
user = www
group = www
mode = 0660
}
unix_listener stats-writer {
user = www
group = www
mode = 0660
}
}
service managesieve-login {
inet_listener sieve {
address = 127.0.0.1
port = 4190
}
}
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext
EOF
cat <<'EOF' > /usr/local/etc/dovecot/dovecot-pgsql.conf
driver = pgsql
connect = host=localhost port=5432 dbname=postfixadmin user=postfix password=__PASSWORD_POSTFIX__
default_pass_scheme = ARGON2ID
iterate_query = \
SELECT username AS user, \
FROM mailbox \
WHERE username = '%u' \
AND (('%s' = 'smtp' AND smtp_active = true) \
OR ('%s' <> 'smtp' AND active = true))
user_query = WITH candidate AS ( \
SELECT \
0 AS prio, \
m.username AS user, \
'/var/vmail/' || m.maildir AS home, \
5000 AS uid, \
5000 AS gid, \
'*:bytes=' || m.quota AS quota_rule \
FROM mailbox m \
WHERE m.username = '%u' \
AND (('%s' = 'smtp' AND m.smtp_active = true) \
OR ('%s' <> 'smtp' AND m.active = true)) \
UNION ALL \
SELECT \
1 AS prio, \
m.username AS user, \
'/var/vmail/' || m.maildir AS home, \
5000 AS uid, \
5000 AS gid, \
'*:bytes=' || m.quota AS quota_rule \
FROM alias a \
JOIN mailbox m \
ON m.username = a.goto \
WHERE a.address = '%u' \
AND (('%s' = 'smtp' AND m.smtp_active = true) \
OR ('%s' <> 'smtp' AND m.active = true)) \
) \
SELECT user, home, uid, gid, quota_rule \
FROM candidate \
ORDER BY prio \
LIMIT 1;
password_query = WITH candidate AS ( \
SELECT \
0 AS prio, \
m.username AS user, \
m.password AS password, \
'/var/vmail/' || m.maildir AS userdb_home, \
5000 AS userdb_uid, \
5000 AS userdb_gid, \
'*:bytes=' || m.quota AS userdb_quota_rule \
FROM mailbox m \
WHERE m.username = '%u' \
AND (('%s' = 'smtp' AND m.smtp_active = true) \
OR ('%s' <> 'smtp' AND m.active = true)) \
UNION ALL \
SELECT \
1 AS prio, \
m.username AS user, \
m.password AS password, \
'/var/vmail/' || m.maildir AS userdb_home, \
5000 AS userdb_uid, \
5000 AS userdb_gid, \
'*:bytes=' || m.quota AS userdb_quota_rule \
FROM alias a \
JOIN mailbox m \
ON m.username = a.goto \
WHERE a.address = '%u' \
AND (('%s' = 'smtp' AND m.smtp_active = true) \
OR ('%s' <> 'smtp' AND m.active = true)) \
) \
SELECT user, password, userdb_home, userdb_uid, userdb_gid, userdb_quota_rule \
FROM candidate \
ORDER BY prio \
LIMIT 1;
EOF
cat <<'EOF' > /usr/local/etc/dovecot/dovecot-dict-quota.conf
connect = host=localhost port=5432 dbname=postfixadmin user=postfix password=__PASSWORD_POSTFIX__
map {
pattern = priv/quota/storage
table = quota2
username_field = username
value_field = bytes
}
map {
pattern = priv/quota/messages
table = quota2
username_field = username
value_field = messages
}
EOF
cat <<'EOF' > /usr/local/etc/dovecot/dovecot-last-login.conf
driver = pgsql
connect = host=localhost port=5432 dbname=postfixadmin user=postfix password=__PASSWORD_POSTFIX__
map {
pattern = shared/last-login/imap/$user/$domain
table = last_login
value_field = imap
value_type = uint
fields {
username = $user
domain = $domain
}
}
map {
pattern = shared/last-login/pop3/$user/$domain
table = last_login
value_field = pop3
value_type = uint
fields {
username = $user
domain = $domain
}
}
map {
pattern = shared/last-login/lda/$user/$domain
table = last_login
value_field = lda
value_type = uint
fields {
username = $user
domain = $domain
}
}
map {
pattern = shared/last-login/lmtp/$user/$domain
table = last_login
value_field = lda
value_type = uint
fields {
username = $user
domain = $domain
}
}
EOF
cat <<'EOF' > /usr/local/etc/dovecot/dovecot-share-folder.conf
driver = pgsql
connect = host=localhost port=5432 dbname=postfixadmin user=postfix password=__PASSWORD_POSTFIX__
map {
pattern = shared/shared-boxes/user/$to/$from
table = share_folder
value_field = dummy
fields {
from_user = $from
to_user = $to
}
}
map {
pattern = shared/shared-boxes/anyone/$from
table = anyone_shares
value_field = dummy
fields {
from_user = $from
}
}
EOF
cat <<'EOF' > /usr/local/etc/dovecot/dovecot-used-quota.conf
driver = pgsql
connect = host=localhost port=5432 dbname=postfixadmin user=postfix password=__PASSWORD_POSTFIX__
map {
pattern = priv/quota/storage
table = used_quota
username_field = username
value_field = bytes
}
map {
pattern = priv/quota/messages
table = used_quota
username_field = username
value_field = messages
}
EOF
chown www:www /var/run/dovecot/stats-reader /var/run/dovecot/stats-writer
chmod 660 /var/run/dovecot/stats-reader /var/run/dovecot/stats-writer
mkdir -p /usr/local/etc/dovecot/sieve
cat <<'EOF' > /usr/local/etc/dovecot/sieve/before-global.sieve
require "fileinto";
if header :contains "X-Spam-Flag" "YES"
{
fileinto "Junk";
stop;
}
EOF
sievec /usr/local/etc/dovecot/sieve/before-global.sieve
Dovecot liest seine Konfiguration aus dovecot.conf; für virtuelle Benutzer sind SQL-basierte passdb- und userdb-Lookups ein üblicher Aufbau. doveconf ist das vorgesehene Werkzeug, um die tatsächlich geparste Konfiguration auszugeben. (Dovecot Pro)
Platzhalter in den Konfigurationsdateien ersetzen¶
# Standard-Interface ermitteln
DEF_IF="$(route -n get -inet default | awk '/interface:/ {print $2}')"
# Primäre IPv4-Adresse ermitteln
IP4="$(ifconfig "$DEF_IF" inet | awk '/inet / && $2 !~ /^127\./ {print $2}' | head -n 1)"
[ -n "$IP4" ] && sed -e "s|__IPADDR4__|$IP4|g" -i '' /usr/local/etc/dovecot/*.conf
# Primäre globale IPv6-Adresse ermitteln
IP6="$(ifconfig "$DEF_IF" inet6 | awk '/inet6 / && $2 !~ /^fe80:/ && $2 !~ /^::1/ {print $2}' | head -n 1)"
[ -n "$IP6" ] && sed -e "s|__IPADDR6__|$IP6|g" -i '' /usr/local/etc/dovecot/*.conf
# Passwort für PostgreSQL-User postfix einsetzen
cat /var/db/passwords/user_postgresql_postfix | xargs -I % \
sed -e "s|__PASSWORD_POSTFIX__|%|g" -i '' /usr/local/etc/dovecot/*.conf
# Passwort anzeigen
cat /var/db/passwords/user_postgresql_postfix
Quota-Warning-Script einrichten¶
cat <<'EOF' > /usr/local/bin/dovecot-quota-warning.sh
#!/usr/local/bin/bash
# Purpose: Mail to user when quota exceeds specified percentage
# Reference: https://doc.dovecot.org/configuration_manual/quota/
# Location: /usr/local/bin/dovecot-quota-warning.sh
# Permissions: chown root:vmail && chmod 750
set -euo pipefail
# ============================================================================
# CONFIGURATION
# ============================================================================
LOG_FILE="/var/log/dovecot/quota-warnings.log"
POSTMASTER_NOTIFY_THRESHOLD=95
DOVECOT_LDA="/usr/local/libexec/dovecot/dovecot-lda"
HOSTNAME_FQDN="$(hostname -f)"
# ============================================================================
# LOGGING FUNCTION
# ============================================================================
log_message() {
local level="${1}"
local message="${2}"
local timestamp
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
echo "[${timestamp}] [${level}] ${message}" >> "${LOG_FILE}"
}
# ============================================================================
# INPUT VALIDATION
# ============================================================================
PERCENT="${1:-}"
USER="${2:-}"
# Validate inputs
if [[ -z "${PERCENT}" || -z "${USER}" ]]; then
log_message "ERROR" "Missing arguments: PERCENT=${PERCENT:-EMPTY} USER=${USER:-EMPTY}"
exit 1
fi
# Validate PERCENT is numeric
if ! [[ "${PERCENT}" =~ ^[0-9]+$ ]]; then
log_message "ERROR" "Invalid PERCENT value: ${PERCENT}"
exit 1
fi
# Validate USER contains @
if ! [[ "${USER}" =~ ^[^@]+@[^@]+$ ]]; then
log_message "ERROR" "Invalid USER format: ${USER}"
exit 1
fi
log_message "INFO" "Quota warning triggered: USER=${USER} PERCENT=${PERCENT}%"
# ============================================================================
# QUOTA CONFIGURATION (MUST MATCH dovecot.conf plugin.quota)
# ============================================================================
# Note: Remove :noenforcing to match main quota configuration
QUOTA_CONFIG="dict:storage=2G quota::proxy::quota"
# ============================================================================
# SEND WARNING TO USER
# ============================================================================
send_user_warning() {
local user="${1}"
local percent="${2}"
log_message "INFO" "Sending quota warning to ${user} (${percent}%)"
if ! ${DOVECOT_LDA} -d "${user}" -o "plugin/quota=${QUOTA_CONFIG}" << EOF
From: no-reply@${HOSTNAME_FQDN}
To: ${user}
Subject: Warning: Your mailbox is now ${percent}% full
Auto-Submitted: auto-generated
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Your mailbox is now ${percent}% full.
Current usage: ${percent}% of allocated quota
Threshold: This warning was triggered at ${percent}%
Please delete old messages or archive them to free up space.
If you need additional storage, contact your administrator.
--
Automated quota warning from ${HOSTNAME_FQDN}
EOF
then
log_message "ERROR" "Failed to send quota warning to ${user}"
return 1
fi
log_message "INFO" "Quota warning sent successfully to ${user}"
return 0
}
# ============================================================================
# SEND COPY TO POSTMASTER (High threshold only)
# ============================================================================
send_postmaster_warning() {
local user="${1}"
local percent="${2}"
local domain
domain="$(echo "${user}" | awk -F'@' '{print $2}')"
local postmaster="postmaster@${domain}"
log_message "INFO" "Sending postmaster notification for ${user} (${percent}%)"
if ! ${DOVECOT_LDA} -d "${postmaster}" -o "plugin/quota=${QUOTA_CONFIG}" << EOF
From: no-reply@${HOSTNAME_FQDN}
To: ${postmaster}
Subject: CRITICAL: Mailbox Quota Warning - ${percent}% full (${user})
Auto-Submitted: auto-generated
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
X-Priority: 1 (Highest)
CRITICAL QUOTA WARNING
======================
User Account: ${user}
Domain: ${domain}
Current Usage: ${percent}% of allocated quota
Threshold Triggered: ${POSTMASTER_NOTIFY_THRESHOLD}%
Timestamp: $(date '+%Y-%m-%d %H:%M:%S %Z')
ACTION REQUIRED:
This user's mailbox is critically full. Please contact the user to:
1. Delete unnecessary messages
2. Archive old emails
3. Request quota increase if needed
Failure to act may result in bounced incoming mail.
--
Automated quota alert from ${HOSTNAME_FQDN}
Dovecot Quota Warning System
EOF
then
log_message "ERROR" "Failed to send postmaster notification for ${user}"
return 1
fi
log_message "INFO" "Postmaster notification sent successfully for ${user}"
return 0
}
# ============================================================================
# MAIN EXECUTION
# ============================================================================
main() {
# Ensure log directory exists
mkdir -p "$(dirname "${LOG_FILE}")"
# Send warning to user (all thresholds)
if ! send_user_warning "${USER}" "${PERCENT}"; then
log_message "ERROR" "User warning delivery failed"
exit 1
fi
# Send postmaster copy only for critical thresholds
if [[ "${PERCENT}" -ge "${POSTMASTER_NOTIFY_THRESHOLD}" ]]; then
if ! send_postmaster_warning "${USER}" "${PERCENT}"; then
log_message "ERROR" "Postmaster notification failed"
# Don't exit - user warning was successful
fi
fi
log_message "INFO" "Quota warning processing completed for ${USER}"
}
main
exit 0
EOF
chown root:vmail /usr/local/bin/dovecot-quota-warning.sh
chmod 750 /usr/local/bin/dovecot-quota-warning.sh
mkdir -p /var/log/dovecot
Master User einrichten¶
cat <<'EOF' > /usr/local/etc/dovecot/dovecot-master-users
superuser:__PASSWORD_SUPERUSER__
EOF
# Passwort für den Master-User "superuser" erzeugen und
# in /var/db/passwords/user_dovecot_superuser speichern
install -b -m 0640 /dev/null /var/db/passwords/user_dovecot_superuser
openssl rand -hex 64 | openssl passwd -5 -stdin | tr -cd '[[:print:]]' | \
cut -c 2-17 | tee /var/db/passwords/user_dovecot_superuser | xargs -I % \
doveadm pw -s SSHA512 -p % | xargs -I % echo "superuser:%" | \
tee /usr/local/etc/dovecot/dovecot-master-users
Konfiguration prüfen¶
Vor dem ersten Start sollte die effektive Konfiguration geprüft werden. Dovecot empfiehlt dafür doveconf, weil damit sichtbar wird, was der Dienst nach dem Parsen der Konfiguration tatsächlich verwendet. (Dovecot Pro)
Datenbanken¶
Virtuelle Mailuser anlegen¶
Bei virtuellen Benutzern sind SQL-Backends üblich. Dovecot nutzt dabei typischerweise SQL für passdb und userdb; die Datenbank liefert dabei unter anderem Benutzername, Passwort, UID, GID und Mailpfad. Genau dafür ist das folgende Hilfsscript in diesem Setup vorgesehen. (Dovecot)
cat <<'EOF' > /usr/local/etc/dovecot/create_mailuser.sh
#!/bin/sh
set -eu
dovecot_user="${1}"
localpart="${dovecot_user%@*}"
domain="${dovecot_user#*@}"
home="/var/vmail/${domain}/${localpart}"
dovecot_pass="$(openssl rand -hex 64 | openssl passwd -5 -stdin | tr -cd '[[:print:]]' | cut -c 3-18)"
dovecot_hash="$(doveadm pw -s ARGON2ID -p "${dovecot_pass}")"
echo "Password for ${dovecot_user} is: ${dovecot_pass}"
echo "${dovecot_user}:${dovecot_hash}:5000:5000::${home}::" >> /usr/local/etc/dovecot/passwd
exit 0
EOF
chmod 755 /usr/local/etc/dovecot/create_mailuser.sh
Beispiel zum Anlegen eines neuen Mailusers:
Das Löschen von Mailusern ist in dieser Fassung weiterhin nicht implementiert.
Zusatzsoftware¶
Mögliche Zusatzsoftware wird hier installiert und konfiguriert.
Für dieses HowTo ist keine zusätzliche Software erforderlich.
Pigeonhole ist bereits Bestandteil der Installation und ergänzt Dovecot um Sieve und ManageSieve. ManageSieve wird verwendet, um die Sieve-Skripte der Benutzer zu verwalten; der Interpreter selbst arbeitet mit Dovecots LDA und LMTP zusammen. (Dovecot)
Aufräumen¶
Überflüssige oder temporäre Verzeichnisse und Dateien entsorgen.
Zusatzsoftware Installation¶
Nicht erforderlich.
Zusatzsoftware Konfiguration¶
Nicht erforderlich.
Abschluss¶
Dovecot kann nun gestartet werden.
Für spätere Änderungen:
Referenzen¶
- FreshPorts:
mail/dovecot - FreshPorts:
mail/dovecot-pigeonhole - Dovecot CE: Virtual Users
- Dovecot CE: SQL Authentication
- Dovecot CE: Pigeonhole / Sieve
- Dovecot CE: ManageSieve
- Dovecot CE / Pro:
doveconf(1)und Konfigurationsprüfung