Zum Inhalt

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 vmail mit UID/GID 5000 existiert bereits.
  • Die TLS-Zertifikate für mail.example.com existieren 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.

Text Only
mail.example.com.       IN  A       __IPADDR4__
mail.example.com.       IN  AAAA    __IPADDR6__

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.

Bash
pw groupshow vmail
id vmail

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.

Bash
install -b -m 0640 /dev/null /var/db/passwords/user_dovecot_superuser

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.

Bash

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.

Bash


Installation

Wir installieren mail/dovecot@pgsql und dessen Abhängigkeiten.

Bash
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.

Bash
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.

Bash
sysrc dovecot_enable=YES

Konfiguration

Konfigurationsdateien

Bash
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

Bash
# 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

Bash
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

Bash
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)

Bash
doveconf -n
service dovecot start
sockstat -4 -6 -l | egrep 'dovecot|imap|lmtp|sieve'

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)

Bash
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:

Bash
/usr/local/etc/dovecot/create_mailuser.sh admin@example.com

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.

Bash
service dovecot start

Für spätere Änderungen:

Bash
service dovecot reload
service dovecot restart

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