dovecot: migrate to dovecot 2.4
This commit is contained in:
+25
-30
@@ -587,6 +587,7 @@ in
|
||||
Full text search indexing with Xapian through the fts_flatcurve plugin.
|
||||
This has significant performance and disk space cost.
|
||||
'';
|
||||
|
||||
memoryLimit = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
@@ -603,31 +604,13 @@ in
|
||||
default = true;
|
||||
description = "Enable automatic indexing of messages as they are received or modified.";
|
||||
};
|
||||
autoIndexExclude = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [
|
||||
"\\Trash"
|
||||
"SomeFolder"
|
||||
"Other/*"
|
||||
];
|
||||
description = ''
|
||||
Mailboxes to exclude from automatic indexing.
|
||||
'';
|
||||
};
|
||||
|
||||
enforced = mkOption {
|
||||
type = types.enum [
|
||||
"yes"
|
||||
"no"
|
||||
"body"
|
||||
];
|
||||
default = "no";
|
||||
fallback = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Fail searches when no index is available. If set to
|
||||
`body`, then only body searches (as opposed to
|
||||
header) are affected. If set to `no`, searches may
|
||||
fall back to a very slow brute force search.
|
||||
Whether to fallback to slow non-indexed search, if FTS lookup and
|
||||
indexing have failed.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -640,9 +623,11 @@ in
|
||||
];
|
||||
description = ''
|
||||
A list of languages that the full text search should detect.
|
||||
At least one language must be specified.
|
||||
The language listed first is the default and is used when language recognition fails.
|
||||
See <https://doc.dovecot.org/main/core/plugins/fts.html#fts_languages>.
|
||||
|
||||
At least one language must be specified. The language listed first is
|
||||
the default and is used when language recognition fails.
|
||||
|
||||
See <https://doc.dovecot.org/main/core/plugins/fts.html#languages>.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -679,8 +664,8 @@ in
|
||||
"stopwords"
|
||||
];
|
||||
description = ''
|
||||
The list of filters to apply.
|
||||
<https://doc.dovecot.org/main/core/plugins/fts.html#filter-configuration>.
|
||||
The list of language filters to apply.
|
||||
See <https://doc.dovecot.org/main/core/plugins/fts.html#filter-configuration>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
@@ -825,9 +810,9 @@ in
|
||||
directoryLayout = mkOption {
|
||||
type = types.enum [
|
||||
"fs"
|
||||
"maildir++"
|
||||
"Maildir++"
|
||||
];
|
||||
default = "maildir++";
|
||||
default = "Maildir++";
|
||||
description = ''
|
||||
Sets whether dovecot should organize mail in subdirectories:
|
||||
|
||||
@@ -924,10 +909,12 @@ in
|
||||
Trash = {
|
||||
auto = "no";
|
||||
special_use = "\\Trash";
|
||||
fts_autoindex = false;
|
||||
};
|
||||
Junk = {
|
||||
auto = "subscribe";
|
||||
special_use = "\\Junk";
|
||||
fts_autoindex = false;
|
||||
};
|
||||
Drafts = {
|
||||
auto = "subscribe";
|
||||
@@ -1784,5 +1771,13 @@ in
|
||||
(mkChangedOptionModule [ "mailserver" "useFSLayout" ] [ "mailserver" "storage" "directoryLayout" ] (
|
||||
config: if config.mailserver.useFSLayout then "fs" else "maildir++"
|
||||
))
|
||||
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "enforced" ] ''
|
||||
Whether to fallback to non-indexed search is now controlled via
|
||||
`mailserver.fullTextSearch.fallback`. Missing mails are now always indexed,
|
||||
since flatcurve is very fast.
|
||||
'')
|
||||
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "autoIndexExclude" ] ''
|
||||
Configure `fts_autoindex` on mail directories in `mailserver.mailboxes` instead.
|
||||
'')
|
||||
];
|
||||
}
|
||||
|
||||
+2
-2
@@ -20,13 +20,13 @@ To enable indexing for full text search here is an example configuration.
|
||||
enable = true;
|
||||
# index new email as they arrive
|
||||
autoIndex = true;
|
||||
enforced = "body";
|
||||
fallback = false;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
The ``enforced`` parameter tells dovecot to fail any body search query that cannot
|
||||
The ``fallback`` parameter tells dovecot to fail any body search query that cannot
|
||||
use an index. This prevents dovecot to fall back to the IO-intensive brute
|
||||
force search.
|
||||
|
||||
|
||||
+318
-309
@@ -39,6 +39,7 @@ let
|
||||
mapAttrs'
|
||||
mkForce
|
||||
mkIf
|
||||
mkMerge
|
||||
nameValuePair
|
||||
;
|
||||
|
||||
@@ -47,56 +48,6 @@ let
|
||||
passwdDir = "/run/dovecot2";
|
||||
passwdFile = "${passwdDir}/passwd";
|
||||
userdbFile = "${passwdDir}/userdb";
|
||||
# This file contains the ldap bind password
|
||||
ldapConfFile = "${passwdDir}/dovecot-ldap.conf.ext";
|
||||
listToMultiAttrs =
|
||||
keyPrefix: attrs:
|
||||
lib.listToAttrs (
|
||||
lib.imap1 (n: x: {
|
||||
name = "${keyPrefix}${if n == 1 then "" else toString n}";
|
||||
value = x;
|
||||
}) attrs
|
||||
);
|
||||
|
||||
maildirLayoutAppendix = lib.optionalString (cfg.storage.directoryLayout == "fs") ":LAYOUT=fs";
|
||||
maildirUTF8FolderNames = lib.optionalString cfg.useUTF8FolderNames ":UTF-8";
|
||||
|
||||
ldapConfig = pkgs.writeTextFile {
|
||||
name = "dovecot-ldap.conf.ext.template";
|
||||
text = ''
|
||||
ldap_version = 3
|
||||
uris = ${lib.concatStringsSep " " cfg.ldap.uris}
|
||||
${lib.optionalString cfg.ldap.startTls ''
|
||||
tls = yes
|
||||
''}
|
||||
tls_require_cert = hard
|
||||
tls_ca_cert_file = ${cfg.ldap.caFile}
|
||||
dn = ${cfg.ldap.bind.dn}
|
||||
sasl_bind = no
|
||||
auth_bind = yes
|
||||
base = ${cfg.ldap.base}
|
||||
scope = ${mkLdapSearchScope cfg.ldap.scope}
|
||||
user_attrs = \
|
||||
=home=${cfg.storage.path}/ldap/%{ldap:${cfg.ldap.attributes.uuid}}, \
|
||||
=mail=maildir:~/mail${maildirLayoutAppendix}${maildirUTF8FolderNames}${
|
||||
lib.optionalString (
|
||||
cfg.indexDir != null
|
||||
) ":INDEX=${cfg.indexDir}/ldap/%{ldap:${cfg.ldap.attributes.uuid}}"
|
||||
}
|
||||
user_filter = ${cfg.ldap.dovecot.userFilter}
|
||||
pass_attrs = ${cfg.ldap.attributes.password}=password
|
||||
pass_filter = ${cfg.ldap.dovecot.passFilter}
|
||||
'';
|
||||
};
|
||||
|
||||
setPwdInLdapConfFile = appendLdapBindPwd {
|
||||
name = "ldap-conf-file";
|
||||
file = ldapConfig;
|
||||
prefix = ''dnpass = "'';
|
||||
suffix = ''"'';
|
||||
passwordFile = cfg.ldap.bind.passwordFile;
|
||||
destination = ldapConfFile;
|
||||
};
|
||||
|
||||
genPasswdScript =
|
||||
pkgs.writeScript "generate-password-file"
|
||||
@@ -130,7 +81,7 @@ let
|
||||
if lib.elem name accountsWithPlaintextPasswordFiles then
|
||||
"${name}:${"$(sed -n '1{p;p;q}' ${passwordFiles."${name}"} | ${lib.getExe' config.services.dovecot2.package "doveadm"} pw)"}::::::"
|
||||
else
|
||||
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
|
||||
"${name}:{CRYPT}${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
|
||||
) cfg.accounts
|
||||
)}
|
||||
EOF
|
||||
@@ -140,8 +91,11 @@ let
|
||||
${lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (
|
||||
name: value:
|
||||
# https://doc.dovecot.org/2.4.3/core/config/auth/databases/passwd_file.html
|
||||
# https://doc.dovecot.org/2.4.3/core/plugins/quota.html#per-user-quota
|
||||
# https://dovecot.org/mailman3/archives/list/dovecot@dovecot.org/thread/67DBLLW4L5QBTEYRKGA26POFZ52ZR7ZO/#67DBLLW4L5QBTEYRKGA26POFZ52ZR7ZO
|
||||
"${name}:::::::"
|
||||
+ lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}"
|
||||
+ lib.optionalString (value.quota != null) "userdb_quota/user/storage_size=${value.quota}"
|
||||
) cfg.accounts
|
||||
)}
|
||||
EOF
|
||||
@@ -213,310 +167,365 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
# for sieve-test. Shelling it in on demand usually doesn't work, as it reads
|
||||
# the global config and tries to open shared libraries configured in there,
|
||||
# which are usually not compatible.
|
||||
# Dovecot modules
|
||||
environment.systemPackages = [
|
||||
pkgs.dovecot_pigeonhole_0_5
|
||||
]
|
||||
++ lib.optional cfg.fullTextSearch.enable pkgs.dovecot-fts-flatcurve;
|
||||
pkgs.dovecot_pigeonhole
|
||||
];
|
||||
|
||||
# For compatibility with python imaplib
|
||||
environment.etc."dovecot/modules".source = "/run/current-system/sw/lib/dovecot/modules";
|
||||
|
||||
services.dovecot2 = {
|
||||
package = pkgs.dovecot_2_3;
|
||||
enable = true;
|
||||
package = pkgs.dovecot; # pin over stateVersion logic in nixox 26.05
|
||||
enablePAM = mkForce false;
|
||||
|
||||
sieve = {
|
||||
extensions = [
|
||||
"fileinto"
|
||||
];
|
||||
|
||||
scripts.after = builtins.toFile "spam.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "${junkMailboxName}";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
|
||||
pipeBins = map lib.getExe [
|
||||
(pkgs.writeShellScriptBin "rspamd-learn-ham.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_ham")
|
||||
(pkgs.writeShellScriptBin "rspamd-learn-spam.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_spam")
|
||||
];
|
||||
};
|
||||
|
||||
imapsieve.mailbox = [
|
||||
{
|
||||
name = junkMailboxName;
|
||||
causes = [
|
||||
"COPY"
|
||||
"APPEND"
|
||||
];
|
||||
before = ./dovecot/imap_sieve/report-spam.sieve;
|
||||
}
|
||||
{
|
||||
name = "*";
|
||||
from = junkMailboxName;
|
||||
causes = [ "COPY" ];
|
||||
before = ./dovecot/imap_sieve/report-ham.sieve;
|
||||
}
|
||||
sieve.pipeBins = map lib.getExe [
|
||||
(pkgs.writeShellScriptBin "rspamd-learn-ham.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_ham")
|
||||
(pkgs.writeShellScriptBin "rspamd-learn-spam.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_spam")
|
||||
];
|
||||
|
||||
settings = {
|
||||
# vmail user
|
||||
mail_uid = cfg.storage.owner;
|
||||
mail_gid = cfg.storage.group;
|
||||
mail_access_groups = cfg.storage.group;
|
||||
# https://doc.dovecot.org/2.4.3/core/settings/syntax.html
|
||||
# https://doc.dovecot.org/2.4.3/core/settings/types.html#boolean-list
|
||||
settings = mkMerge [
|
||||
({
|
||||
# https://doc.dovecot.org/main/core/summaries/settings.html#dovecot_config_version
|
||||
dovecot_config_version = "2.4.3";
|
||||
# https://doc.dovecot.org/main/core/summaries/settings.html#dovecot_storage_version
|
||||
dovecot_storage_version = "2.3.21.1";
|
||||
|
||||
# authentication
|
||||
auth_mechanisms = [
|
||||
"plain"
|
||||
"login"
|
||||
];
|
||||
disable_plaintext_auth = true;
|
||||
# server identity
|
||||
hostname = cfg.fqdn;
|
||||
|
||||
# hostname
|
||||
hostname = cfg.fqdn;
|
||||
# vmail user
|
||||
mail_uid = cfg.storage.owner;
|
||||
mail_gid = cfg.storage.group;
|
||||
mail_access_groups = cfg.storage.group;
|
||||
|
||||
# backend services
|
||||
"service auth" = {
|
||||
"unix_listener auth" = {
|
||||
mode = "0660";
|
||||
user = config.services.postfix.user;
|
||||
group = config.services.postfix.group;
|
||||
};
|
||||
};
|
||||
"service indexer-worker" = mkIf (cfg.fullTextSearch.memoryLimit != null) {
|
||||
vsz_limit = "${toString cfg.fullTextSearch.memoryLimit} MB";
|
||||
};
|
||||
"service lmtp" = {
|
||||
"unix_listener dovecot-lmtp" = {
|
||||
group = config.services.postfix.group;
|
||||
mode = "0600";
|
||||
user = config.services.postfix.user;
|
||||
};
|
||||
user = cfg.storage.owner;
|
||||
vsz_limit = "${toString cfg.lmtpMemoryLimit} MB";
|
||||
};
|
||||
"service quota-status" = mkIf cfg.quota.enable {
|
||||
executable = toString [
|
||||
"${config.services.dovecot2.package}/libexec/dovecot/quota-status"
|
||||
"-p"
|
||||
"postfix"
|
||||
# authentication
|
||||
auth_mechanisms = [
|
||||
"plain"
|
||||
"login"
|
||||
];
|
||||
"unix_listener quota-status" = {
|
||||
user = "postfix";
|
||||
};
|
||||
client_limit = 1;
|
||||
vsz_limit = "${toString cfg.quotaStatusMemoryLimit} MB";
|
||||
};
|
||||
|
||||
# frontend services
|
||||
"service imap-login" = mkIf (cfg.enableImap || cfg.enableImapSsl) {
|
||||
"inet_listener imap" = {
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = if cfg.enableImap then 143 else 0;
|
||||
# backend services
|
||||
"service anvil" = {
|
||||
"unix_listener anvil" = {
|
||||
mode = "0660";
|
||||
group = cfg.storage.group;
|
||||
};
|
||||
};
|
||||
"inet_listener imaps" = {
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = if cfg.enableImapSsl then 993 else 0;
|
||||
ssl = true;
|
||||
"service auth" = {
|
||||
"unix_listener auth" = {
|
||||
user = config.services.postfix.user;
|
||||
group = config.services.postfix.group;
|
||||
mode = "0660";
|
||||
};
|
||||
};
|
||||
};
|
||||
"service pop3-login" = mkIf (cfg.enablePop3 || cfg.enablePop3Ssl) {
|
||||
"inet_listener pop3" = {
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = if cfg.enablePop3 then 110 else 0;
|
||||
"service lmtp" = {
|
||||
"unix_listener dovecot-lmtp" = {
|
||||
user = config.services.postfix.user;
|
||||
group = config.services.postfix.group;
|
||||
mode = "0600";
|
||||
};
|
||||
user = cfg.storage.owner;
|
||||
vsz_limit = "${toString cfg.lmtpMemoryLimit} MB";
|
||||
};
|
||||
"inet_listener pop3s" = {
|
||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = if cfg.enablePop3Ssl then 995 else 0;
|
||||
ssl = true;
|
||||
};
|
||||
};
|
||||
"service imap" = {
|
||||
vsz_limit = "${toString cfg.imapMemoryLimit} MB";
|
||||
};
|
||||
|
||||
# protocols
|
||||
protocols = [
|
||||
"lmtp"
|
||||
]
|
||||
++ lib.optionals (cfg.enableImap || cfg.enableImapSsl) [ "imap" ]
|
||||
++ lib.optionals (cfg.enablePop3 || cfg.enablePop3Ssl) [ "pop3" ]
|
||||
++ lib.optionals cfg.enableManageSieve [ "sieve" ];
|
||||
# frontend services
|
||||
"service imap-login" = mkIf (cfg.enableImap || cfg.enableImapSsl) {
|
||||
"inet_listener imap" = {
|
||||
# https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = if cfg.enableImap then 143 else 0;
|
||||
};
|
||||
"inet_listener imaps" = mkIf cfg.enableImapSsl {
|
||||
port = 993;
|
||||
ssl = true;
|
||||
};
|
||||
};
|
||||
"service pop3-login" = mkIf (cfg.enablePop3 || cfg.enablePop3Ssl) {
|
||||
"inet_listener pop3" = {
|
||||
# https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||
port = if cfg.enablePop3 then 110 else 0;
|
||||
};
|
||||
"inet_listener pop3s" = mkIf cfg.enablePop3Ssl {
|
||||
port = 995;
|
||||
ssl = true;
|
||||
};
|
||||
};
|
||||
"service imap" = {
|
||||
vsz_limit = "${toString cfg.imapMemoryLimit} MB";
|
||||
};
|
||||
|
||||
"protocol lmtp" = {
|
||||
mail_plugins = [
|
||||
"$mail_plugins"
|
||||
"sieve"
|
||||
# protocols
|
||||
protocols = {
|
||||
lmtp = true;
|
||||
imap = cfg.enableImap || cfg.enableImapSsl;
|
||||
pop3 = cfg.enablePop3 || cfg.enablePop3Ssl;
|
||||
sieve = cfg.enableManageSieve;
|
||||
};
|
||||
|
||||
"protocol lmtp" = {
|
||||
mail_plugins = {
|
||||
sieve = true;
|
||||
};
|
||||
};
|
||||
"protocol imap" = {
|
||||
mail_max_userip_connections = cfg.maxConnectionsPerUser;
|
||||
mail_plugins = {
|
||||
imap_sieve = true;
|
||||
};
|
||||
};
|
||||
"protocol pop3" = {
|
||||
mail_max_userip_connections = cfg.maxConnectionsPerUser;
|
||||
};
|
||||
|
||||
# tls settings
|
||||
ssl_server_cert_file = x509CertificateFile;
|
||||
ssl_server_key_file = x509PrivateKeyFile;
|
||||
# https://ssl-config.mozilla.org/#server=dovecot&version=2.3.21&config=intermediate&openssl=3.4.1&guideline=5.7
|
||||
ssl = "required";
|
||||
ssl_min_protocol = "TLSv1.2";
|
||||
ssl_server_prefer_ciphers = "client";
|
||||
ssl_cipher_list = lib.concatStringsSep ":" [
|
||||
# TLS1.3
|
||||
"TLS_AES_128_GCM_SHA256"
|
||||
"TLS_CHACHA20_POLY1305_SHA256"
|
||||
"TLS_AES_256_GCM_SHA384"
|
||||
# TLS1.2
|
||||
# EC key material
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256"
|
||||
"ECDHE-ECDSA-CHACHA20-POLY1305"
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384"
|
||||
# RSA key material
|
||||
"ECDHE-RSA-AES128-GCM-SHA256"
|
||||
"ECDHE-RSA-CHACHA20-POLY1305"
|
||||
"ECDHE-RSA-AES256-GCM-SHA384"
|
||||
];
|
||||
};
|
||||
"protocol imap" = {
|
||||
mail_max_userip_connections = cfg.maxConnectionsPerUser;
|
||||
mail_plugins = [
|
||||
"$mail_plugins"
|
||||
"imap_sieve"
|
||||
]
|
||||
++ lib.optionals cfg.quota.enable [
|
||||
"imap_quota"
|
||||
ssl_curve_list = lib.concatStringsSep ":" [
|
||||
"X25519MLKEM768"
|
||||
"X25519"
|
||||
"prime256v1"
|
||||
"secp384r1"
|
||||
];
|
||||
};
|
||||
"protocol pop3" = {
|
||||
mail_max_userip_connections = cfg.maxConnectionsPerUser;
|
||||
};
|
||||
|
||||
# globally enabled plugins
|
||||
mail_plugins = [
|
||||
"$mail_plugins"
|
||||
]
|
||||
++ lib.optionals cfg.quota.enable [
|
||||
"quota"
|
||||
]
|
||||
++ lib.optionals cfg.fullTextSearch.enable [
|
||||
"fts"
|
||||
"fts_flatcurve"
|
||||
];
|
||||
# default user mailboxes
|
||||
"namespace inbox" = {
|
||||
inbox = true;
|
||||
separator = cfg.hierarchySeparator;
|
||||
}
|
||||
// mapAttrs' (name: value: nameValuePair ''mailbox "${name}"'' value) cfg.mailboxes;
|
||||
lda_mailbox_autosubscribe = true;
|
||||
lda_mailbox_autocreate = true;
|
||||
|
||||
# tls settings
|
||||
ssl_cert = "<${x509CertificateFile}";
|
||||
ssl_key = "<${x509PrivateKeyFile}";
|
||||
# https://ssl-config.mozilla.org/#server=dovecot&version=2.3.21&config=intermediate&openssl=3.4.1&guideline=5.7
|
||||
ssl = "required";
|
||||
ssl_min_protocol = "TLSv1.2";
|
||||
ssl_prefer_server_ciphers = false;
|
||||
ssl_cipher_list = lib.concatStringsSep ":" [
|
||||
# TLS1.3
|
||||
"TLS_AES_128_GCM_SHA256"
|
||||
"TLS_CHACHA20_POLY1305_SHA256"
|
||||
"TLS_AES_256_GCM_SHA384"
|
||||
# TLS1.2
|
||||
# EC key material
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256"
|
||||
"ECDHE-ECDSA-CHACHA20-POLY1305"
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384"
|
||||
# RSA key material
|
||||
"ECDHE-RSA-AES128-GCM-SHA256"
|
||||
"ECDHE-RSA-CHACHA20-POLY1305"
|
||||
"ECDHE-RSA-AES256-GCM-SHA384"
|
||||
];
|
||||
ssl_curve_list = lib.concatStringsSep ":" [
|
||||
"X25519MLKEM768"
|
||||
"X25519"
|
||||
"prime256v1"
|
||||
"secp384r1"
|
||||
];
|
||||
# subaddressing
|
||||
recipient_delimiter = cfg.recipientDelimiter;
|
||||
lmtp_save_to_detail_mailbox = cfg.lmtpSaveToDetailMailbox;
|
||||
|
||||
# default mail storage
|
||||
# https://doc.dovecot.org/2.3/configuration_manual/home_directories_for_virtual_users/#ways-to-set-up-home-directory
|
||||
mail_location =
|
||||
"maildir:~/mail${maildirLayoutAppendix}${maildirUTF8FolderNames}"
|
||||
+ (lib.optionalString (cfg.indexDir != null) ":INDEX=${cfg.indexDir}/%{domain}/%{username}");
|
||||
# sieve filtering
|
||||
"sieve_script spamfilter" = {
|
||||
# junk filter
|
||||
path = pkgs.writeText "after.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
passdb = [
|
||||
{
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "${junkMailboxName}";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
type = "after";
|
||||
};
|
||||
"sieve_script default" = {
|
||||
# declarative
|
||||
type = "default";
|
||||
path = "${cfg.sieveDirectory}/%{user}/default.sieve";
|
||||
};
|
||||
|
||||
"sieve_script personal" = {
|
||||
# managesieve
|
||||
type = "personal";
|
||||
active_path = "${cfg.sieveDirectory}/%{user}/active.sieve";
|
||||
path = "${cfg.sieveDirectory}/%{user}/scripts";
|
||||
};
|
||||
|
||||
sieve_extensions = {
|
||||
fileinto = true;
|
||||
};
|
||||
sieve_global_extensions = {
|
||||
"vnd.dovecot.pipe" = true;
|
||||
};
|
||||
sieve_plugins = {
|
||||
sieve_imapsieve = true;
|
||||
sieve_extprograms = true;
|
||||
};
|
||||
|
||||
# imapsieve (spam/ham learning)
|
||||
"mailbox ${junkMailboxName}" = {
|
||||
"sieve_script spam" = {
|
||||
cause = [
|
||||
"APPEND"
|
||||
"COPY"
|
||||
];
|
||||
path = ./dovecot/imap_sieve/report-spam.sieve;
|
||||
type = "before";
|
||||
};
|
||||
};
|
||||
"imapsieve_from ${junkMailboxName}" = {
|
||||
"sieve_script ham" = {
|
||||
cause = "copy";
|
||||
path = ./dovecot/imap_sieve/report-ham.sieve;
|
||||
type = "before";
|
||||
};
|
||||
};
|
||||
|
||||
mailbox_list_layout = cfg.storage.directoryLayout;
|
||||
mailbox_list_utf8 = cfg.useUTF8FolderNames;
|
||||
mail_driver = "maildir";
|
||||
mail_path = "~/mail";
|
||||
|
||||
# declarative users
|
||||
"userdb declarative" = {
|
||||
driver = "passwd-file";
|
||||
args = "${passwdFile}";
|
||||
}
|
||||
]
|
||||
++ lib.optionals cfg.ldap.enable [
|
||||
{
|
||||
driver = "ldap";
|
||||
args = "${ldapConfFile}";
|
||||
}
|
||||
];
|
||||
userdb = [
|
||||
{
|
||||
passwd_file_path = userdbFile;
|
||||
fields = {
|
||||
home = "${cfg.storage.path}/%{user | domain}/%{user | username}";
|
||||
inherit (cfg.storage) uid gid;
|
||||
mail_index_path = "${
|
||||
if cfg.indexDir != null then cfg.indexDir else cfg.storage.path
|
||||
}/%{user | domain }/%{user | username}";
|
||||
};
|
||||
};
|
||||
"passdb declarative" = {
|
||||
driver = "passwd-file";
|
||||
args = "${userdbFile}";
|
||||
default_fields = [
|
||||
"home=${cfg.storage.path}/%{domain}/%{username}"
|
||||
"uid=${builtins.toString cfg.storage.uid}"
|
||||
"gid=${builtins.toString cfg.storage.gid}"
|
||||
];
|
||||
}
|
||||
]
|
||||
++ lib.optionals cfg.ldap.enable [
|
||||
{
|
||||
passwd_file_path = passwdFile;
|
||||
};
|
||||
|
||||
})
|
||||
(mkIf cfg.ldap.enable {
|
||||
# ldap users
|
||||
ssl_client_ca_file = cfg.ldap.caFile;
|
||||
ssl_client_require_valid_cert = true;
|
||||
|
||||
ldap_version = 3;
|
||||
ldap_uris = cfg.ldap.uris;
|
||||
ldap_starttls = cfg.ldap.startTls;
|
||||
ldap_auth_dn = cfg.ldap.bind.dn;
|
||||
ldap_auth_dn_password = "</run/credentials/dovecot.service/ldap-bind-pw";
|
||||
ldap_base = cfg.ldap.base;
|
||||
ldap_scope = mkLdapSearchScope cfg.ldap.scope;
|
||||
|
||||
"userdb ldap" = {
|
||||
driver = "ldap";
|
||||
args = "${ldapConfFile}";
|
||||
override_fields = [
|
||||
"uid=${toString cfg.storage.uid}"
|
||||
"gid=${toString cfg.storage.gid}"
|
||||
filter = cfg.ldap.dovecot.userFilter;
|
||||
fields = {
|
||||
home = "${cfg.storage.path}/ldap/%{ldap:${cfg.ldap.attributes.uuid}}";
|
||||
inherit (cfg.storage) uid gid;
|
||||
mail_index_path = "${
|
||||
if cfg.indexDir != null then cfg.indexDir else cfg.storage.path
|
||||
}/ldap/%{ldap:${cfg.ldap.attributes.uuid}}";
|
||||
};
|
||||
ldap_connection_group = "ldap-userdb-conn";
|
||||
};
|
||||
"passdb ldap" = {
|
||||
driver = "ldap";
|
||||
filter = cfg.ldap.dovecot.passFilter;
|
||||
fields = {
|
||||
password = "%{ldap:userPassword}";
|
||||
};
|
||||
ldap_connection_group = "ldap-passdb-conn";
|
||||
};
|
||||
})
|
||||
(mkIf cfg.quota.enable {
|
||||
mail_plugins.quota = true;
|
||||
|
||||
"protocol imap".mail_plugins.imap_quota = true;
|
||||
|
||||
"service quota-status" = {
|
||||
executable = toString [
|
||||
"${config.services.dovecot2.package}/libexec/dovecot/quota-status"
|
||||
"-p"
|
||||
"postfix"
|
||||
];
|
||||
}
|
||||
];
|
||||
"unix_listener quota-status" = {
|
||||
user = "postfix";
|
||||
};
|
||||
client_limit = 1;
|
||||
vsz_limit = "${toString cfg.quotaStatusMemoryLimit} MB";
|
||||
};
|
||||
|
||||
# default user mailboxes
|
||||
"namespace inbox" = {
|
||||
inbox = true;
|
||||
separator = cfg.hierarchySeparator;
|
||||
}
|
||||
// mapAttrs' (name: value: nameValuePair ''mailbox "${name}"'' value) cfg.mailboxes;
|
||||
lda_mailbox_autosubscribe = true;
|
||||
lda_mailbox_autocreate = true;
|
||||
|
||||
# subaddressing
|
||||
recipient_delimiter = cfg.recipientDelimiter;
|
||||
lmtp_save_to_detail_mailbox = cfg.lmtpSaveToDetailMailbox;
|
||||
|
||||
plugin = {
|
||||
sieve = "file:${cfg.sieveDirectory}/%{user}/scripts;active=${cfg.sieveDirectory}/%{user}/active.sieve";
|
||||
sieve_default = "file:${cfg.sieveDirectory}/%{user}/default.sieve";
|
||||
sieve_default_name = "default";
|
||||
}
|
||||
// lib.optionalAttrs cfg.fullTextSearch.enable (
|
||||
{
|
||||
fts = "flatcurve";
|
||||
fts_languages = cfg.fullTextSearch.languages;
|
||||
fts_tokenizers = [
|
||||
"generic"
|
||||
"email-address"
|
||||
];
|
||||
fts_tokenizer_email_address = "maxlen=100"; # default 254 too large for Xapian
|
||||
fts_flatcurve_substring_search = cfg.fullTextSearch.substringSearch;
|
||||
fts_filters = cfg.fullTextSearch.filters;
|
||||
fts_header_excludes = cfg.fullTextSearch.headerExcludes;
|
||||
fts_autoindex = cfg.fullTextSearch.autoIndex;
|
||||
fts_enforced = cfg.fullTextSearch.enforced;
|
||||
}
|
||||
// (listToMultiAttrs "fts_autoindex_exclude" cfg.fullTextSearch.autoIndexExclude)
|
||||
)
|
||||
// lib.optionalAttrs cfg.quota.enable {
|
||||
quota_rule = mkIf (cfg.quota.defaults.perUser != null) "*:storage=${cfg.quota.defaults.perUser}";
|
||||
quota = "count:User quota"; # per virtual mail user quota
|
||||
quota_status_success = "DUNNO";
|
||||
quota_status_nouser = "DUNNO";
|
||||
quota_status_overquota = "552 5.2.2 Mailbox is full";
|
||||
quota_grace = "10%%";
|
||||
quota_vsizes = true;
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs cfg.debug.dovecot {
|
||||
mail_debug = true;
|
||||
auth_debug = true;
|
||||
verbose_ssl = true;
|
||||
};
|
||||
# quota_storage_grace = "10M";
|
||||
|
||||
"quota user" = {
|
||||
driver = "count";
|
||||
storage_size = mkIf (cfg.quota.defaults.perUser != null) cfg.quota.defaults.perUser;
|
||||
};
|
||||
})
|
||||
(mkIf cfg.fullTextSearch.enable (
|
||||
{
|
||||
mail_plugins = {
|
||||
fts = true;
|
||||
fts_flatcurve = true;
|
||||
};
|
||||
|
||||
"service indexer-worker" = mkIf (cfg.fullTextSearch.memoryLimit != null) {
|
||||
vsz_limit = "${toString cfg.fullTextSearch.memoryLimit} MB";
|
||||
};
|
||||
|
||||
fts_autoindex = cfg.fullTextSearch.autoIndex;
|
||||
fts_driver = "flatcurve";
|
||||
fts_search_add_missing = "yes";
|
||||
fts_search_read_fallback = cfg.fullTextSearch.fallback;
|
||||
fts_header_excludes = lib.genAttrs cfg.fullTextSearch.headerExcludes (_: true);
|
||||
|
||||
"fts flatcurve" = {
|
||||
flatcurve_substring_search = cfg.fullTextSearch.substringSearch;
|
||||
};
|
||||
|
||||
# languages
|
||||
language_filters = lib.genAttrs cfg.fullTextSearch.filters (_: true);
|
||||
language_tokenizer_address_token_maxlen = 100; # default 250 too large for Xapian
|
||||
}
|
||||
# build languages from list, the first one becomes the default language
|
||||
// lib.listToAttrs (
|
||||
lib.imap0 (i: lang: {
|
||||
name = "language ${lang}";
|
||||
value = (if i == 0 then { default = true; } else { }) // {
|
||||
language_tokenizers = [
|
||||
"generic"
|
||||
"email-address"
|
||||
];
|
||||
};
|
||||
}) cfg.fullTextSearch.languages
|
||||
)
|
||||
))
|
||||
(mkIf cfg.debug.dovecot {
|
||||
mail_debug = true;
|
||||
# https://doc.dovecot.org/2.4.3/core/config/events/filter.html#common-unified-filter-language
|
||||
log_debug = "category=ssl OR category=auth";
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.dovecot = {
|
||||
preStart = ''
|
||||
${genPasswdScript}
|
||||
''
|
||||
+ (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile);
|
||||
'';
|
||||
reloadTriggers = lib.mkIf (!withACME) [
|
||||
x509CertificateFile
|
||||
x509PrivateKeyFile
|
||||
];
|
||||
serviceConfig = lib.optionalAttrs cfg.ldap.enable {
|
||||
LoadCredential = [
|
||||
"ldap-bind-pw:${cfg.ldap.bind.passwordFile}"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.postfix.restartTriggers = [
|
||||
genPasswdScript
|
||||
]
|
||||
++ (lib.optional cfg.ldap.enable [ setPwdInLdapConfFile ]);
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
+23
-7
@@ -81,7 +81,7 @@
|
||||
};
|
||||
"lowquota@example.com" = {
|
||||
hashedPassword = "$6$u61JrAtuI0a$nGEEfTP5.eefxoScUGVG/Tl0alqla2aGax4oTd85v3j3xSmhv/02gNfSemv/aaMinlv9j/ZABosVKBrRvN5Qv0";
|
||||
quota = "1B";
|
||||
quota = "1K";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -98,13 +98,13 @@
|
||||
fullTextSearch = {
|
||||
enable = true;
|
||||
autoIndex = true;
|
||||
# special use depends on https://github.com/NixOS/nixpkgs/pull/93201
|
||||
autoIndexExclude = [
|
||||
(if (pkgs.lib.versionAtLeast pkgs.lib.version "21") then "\\Junk" else "Junk")
|
||||
];
|
||||
enforced = "yes";
|
||||
fallback = false;
|
||||
};
|
||||
};
|
||||
|
||||
# by default quota can be exceeded once with this amount (default: 10M)
|
||||
# this is required to make the quota subtest hard fail on the first attempt.
|
||||
services.dovecot2.settings.quota_storage_grace = "0";
|
||||
};
|
||||
client =
|
||||
{ nodes, pkgs, ... }:
|
||||
@@ -306,7 +306,21 @@
|
||||
|
||||
Hello User1,
|
||||
|
||||
how are you doing today?
|
||||
how are you doing today? I have this exciting text for you, that helps fill
|
||||
your quota.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
|
||||
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
|
||||
no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit
|
||||
amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut
|
||||
labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam
|
||||
et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata
|
||||
sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur
|
||||
sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore
|
||||
magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo
|
||||
dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est
|
||||
Lorem ipsum dolor sit amet.
|
||||
|
||||
XOXO User1
|
||||
'';
|
||||
@@ -514,6 +528,8 @@
|
||||
client.execute("rm ~/mail/*")
|
||||
client.execute("mv ~/.fetchmailRcLowQuota ~/.fetchmailrc")
|
||||
|
||||
server.log(server.succeed("doveadm quota get -u lowquota@example.com"))
|
||||
|
||||
client.succeed(
|
||||
"msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lowquota@example.com < /etc/root/email2 >&2"
|
||||
)
|
||||
|
||||
+5
-2
@@ -78,6 +78,8 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.dovecot.serviceConfig.CacheDirectory = "dovecot";
|
||||
|
||||
mailserver = {
|
||||
enable = true;
|
||||
fqdn = "mail.example.com";
|
||||
@@ -114,7 +116,7 @@ in
|
||||
group = "vmail";
|
||||
};
|
||||
|
||||
indexDir = "/var/lib/dovecot/indices";
|
||||
indexDir = "/var/cache/dovecot/fts";
|
||||
|
||||
enableImap = false;
|
||||
};
|
||||
@@ -219,7 +221,8 @@ in
|
||||
with subtest("Check dovecot maildir and index locations"):
|
||||
# If these paths change we need a migration
|
||||
machine.succeed("doveadm user -f home user1@example.com | grep ${nodes.machine.mailserver.storage.path}/example.com/user1")
|
||||
machine.succeed("doveadm user -f mail user1@example.com | grep 'maildir:~/mail:INDEX=${nodes.machine.mailserver.indexDir}/example.com/user1'")
|
||||
machine.succeed("doveadm user -f mail_path user1@example.com | grep ${nodes.machine.mailserver.storage.path}/example.com/user1/mail")
|
||||
machine.succeed("doveadm user -f mail_index_path user1@example.com | grep ${nodes.machine.mailserver.indexDir}/example.com/user1")
|
||||
|
||||
with subtest("mail to send only accounts is rejected"):
|
||||
machine.wait_for_open_port(25)
|
||||
|
||||
+18
-9
@@ -18,7 +18,6 @@ let
|
||||
alicePassword = "testalice";
|
||||
bobPassword = "testbob";
|
||||
carolPassword = "testcarol";
|
||||
frankPassword = "testfrank";
|
||||
malloryPassword = "testmallory";
|
||||
in
|
||||
{
|
||||
@@ -76,7 +75,8 @@ in
|
||||
objectClass: simpleSecurityObject
|
||||
objectClass: top
|
||||
cn: mail
|
||||
userPassword: ${bindPassword}
|
||||
# unsafegibberish
|
||||
userPassword: {SSHA}JNr6l3s/RHo1LKRXqFsJg8sXznyRid8L
|
||||
|
||||
dn: ou=users,dc=example
|
||||
objectClass: organizationalUnit
|
||||
@@ -88,7 +88,8 @@ in
|
||||
uid: alice
|
||||
sn: Foo
|
||||
mail: alice@example.com
|
||||
userPassword: ${alicePassword}
|
||||
# testalice
|
||||
userPassword: {SSHA}gkJq4Dm4jfIKjxviR0WD63wMt0Ti6zMB
|
||||
|
||||
dn: cn=bob,ou=users,dc=example
|
||||
entryUUID: f3b4e8ea-087f-42cc-95f0-cbfd99386092
|
||||
@@ -100,7 +101,8 @@ in
|
||||
sn: Bar
|
||||
mail: bob@example.com
|
||||
homeDirectory: /home/bob
|
||||
userPassword: ${bobPassword}
|
||||
# testbob
|
||||
userPassword: {SSHA}qqUveZGZrDrjYFnREXLDZc//y89RppVN
|
||||
|
||||
dn: cn=carol,ou=users,dc=example
|
||||
entryUUID: 41240499-27e2-4fa2-be4f-4113a77661b1
|
||||
@@ -108,7 +110,8 @@ in
|
||||
uid: carol
|
||||
sn: Baz
|
||||
mail: carol@example.com
|
||||
userPassword: ${carolPassword}
|
||||
# testcarol
|
||||
userPassword: {SSHA}69HOuP+OPWE+3+tDucFZxzXDC7p4e3ML
|
||||
|
||||
dn: cn=frank,ou=users,dc=example
|
||||
entryUUID: ca16f594-f6b2-418f-87d3-0d02d746461f
|
||||
@@ -116,17 +119,23 @@ in
|
||||
uid: frank
|
||||
sn: Moo
|
||||
mail: frank@example.com
|
||||
userPassword: ${frankPassword}
|
||||
# testfrank
|
||||
userPassword: {SSHA}xqtMl8/uJ6HEFWDzLYpAE+Wq7FvKrtkm
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.dovecot.serviceConfig = {
|
||||
CacheDirectory = "dovecot";
|
||||
StateDirectory = "dovecot";
|
||||
};
|
||||
|
||||
mailserver = {
|
||||
enable = true;
|
||||
fqdn = "mail.example.com";
|
||||
domains = [ "example.com" ];
|
||||
localDnsResolver = false;
|
||||
storage.path = "/var/lib/dovecot/vmail";
|
||||
indexDir = "/var/lib/dovecot/indices";
|
||||
indexDir = "/var/cache/dovecot/indices";
|
||||
|
||||
aliases = {
|
||||
# Steal frank@example.com from LDAP user frank
|
||||
@@ -215,11 +224,11 @@ in
|
||||
machine.succeed("doveadm user -f gid bob@example.com | grep ${toString nodes.machine.mailserver.storage.uid}")
|
||||
|
||||
machine.succeed("doveadm user -f home bob@example.com | grep ${nodes.machine.mailserver.storage.path}/ldap/f3b4e8ea-087f-42cc-95f0-cbfd99386092")
|
||||
machine.succeed("doveadm user -f mail bob@example.com | grep 'maildir:~/mail:INDEX=${nodes.machine.mailserver.indexDir}/ldap/f3b4e8ea-087f-42cc-95f0-cbfd99386092'")
|
||||
machine.succeed("doveadm user -f mail_path bob@example.com | grep ${nodes.machine.mailserver.storage.path}/ldap/f3b4e8ea-087f-42cc-95f0-cbfd99386092")
|
||||
machine.succeed("doveadm user -f mail_index_path bob@example.com | grep ${nodes.machine.mailserver.indexDir}/ldap/f3b4e8ea-087f-42cc-95f0-cbfd99386092")
|
||||
|
||||
with subtest("Files containing secrets are only readable by root"):
|
||||
machine.succeed("ls -l /run/postfix/*.cf | grep -e '-rw------- 1 root root'")
|
||||
machine.succeed("ls -l /run/dovecot2/dovecot-ldap.conf.ext | grep -e '-rw------- 1 root root'")
|
||||
|
||||
with subtest("Test account/mail address binding via explicit TLS"):
|
||||
machine.fail(" ".join([
|
||||
|
||||
Reference in New Issue
Block a user