diff --git a/default.nix b/default.nix index 82e40b6..e454458 100644 --- a/default.nix +++ b/default.nix @@ -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 . + + At least one language must be specified. The language listed first is + the default and is used when language recognition fails. + + See . ''; }; @@ -679,8 +664,8 @@ in "stopwords" ]; description = '' - The list of filters to apply. - . + The list of language filters to apply. + See . ''; }; }; @@ -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. + '') ]; } diff --git a/docs/fts.rst b/docs/fts.rst index 40a3261..35fd558 100644 --- a/docs/fts.rst +++ b/docs/fts.rst @@ -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. diff --git a/mail-server/dovecot.nix b/mail-server/dovecot.nix index 60bd968..e899487 100644 --- a/mail-server/dovecot.nix +++ b/mail-server/dovecot.nix @@ -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 = "&2" ) diff --git a/tests/internal.nix b/tests/internal.nix index 4fe9bcb..62dc6d8 100644 --- a/tests/internal.nix +++ b/tests/internal.nix @@ -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) diff --git a/tests/ldap.nix b/tests/ldap.nix index 3a63f53..5df5580 100644 --- a/tests/ldap.nix +++ b/tests/ldap.nix @@ -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([