dovecot: polish conf and add mailStorageDirectory support
authorJulien Moutinho <julm@sourcephile.fr>
Fri, 31 Jan 2020 01:24:10 +0000 (02:24 +0100)
committerJulien Moutinho <julm@sourcephile.fr>
Fri, 31 Jan 2020 01:24:10 +0000 (02:24 +0100)
nixos/modules/services/mail/dovecot.nix
servers/mermet/dovecot.nix
servers/mermet/dovecot/ldap/sourcephile.conf [new file with mode: 0644]
servers/mermet/dovecot/sieve/global/extension.sieve [new file with mode: 0644]
servers/mermet/dovecot/sieve/global/flag-spam.sieve [new file with mode: 0644]
servers/mermet/dovecot/sieve/global/list.sieve [new file with mode: 0644]
servers/mermet/dovecot/sieve/global/report-ham.sieve [new file with mode: 0644]
servers/mermet/dovecot/sieve/global/report-spam.sieve [new file with mode: 0644]
servers/mermet/dovecot/sieve/global/spam-or-ham.sieve [new file with mode: 0644]
servers/mermet/openldap/sourcephile.nix

index 7aede87de32e2d038f868dc282b4dd36cbe232f9..d57468c08ebbdb788e2a8a16c88d44c135225b15 100644 (file)
@@ -5,10 +5,7 @@ let
   inherit (pkgs.lib) unlinesAttrs unlinesValues unwords;
   inherit (config.services) dovecot2 openldap;
   inherit (config) networking;
-  stateDir    = "/var/lib/dovecot";
-  mailDir     = "${stateDir}/mail";
-  sieveDir    = "${stateDir}/sieve";
-  authDir     = "${stateDir}/auth";
+  stateDir = "/var/lib/dovecot";
   escapeGroup = lib.stringAsChars (c: if "a"<=c && c<="z"
                                       || "0"<=c && c<="9"
                                       || c=="-"
@@ -47,18 +44,6 @@ options.services.dovecot2 = {
                   obvious meaning. Leave blank for the standard quota `100G`.
                 '';
               };
-              sieves = lib.mkOption {
-                type    = with types; attrsOf str;
-                default = { main = ''
-                              require ["include"];
-  
-                              #include :personal "roundcube";
-                              include :global "spam";
-                              include :global "list";
-                              include :global "extension";
-                            '';
-                          };
-              };
               groups = lib.mkOption {
                 type    = with types; listOf str;
                 default = [];
@@ -69,60 +54,25 @@ options.services.dovecot2 = {
       };
     }));
   };
-  sieves = {
-    global = lib.mkOption {
-      description = "global scripts.";
-      type = types.attrsOf types.str;
-      default = {};
-    };
-    before = lib.mkOption {
-      description = "before scripts.";
-      type = types.attrsOf types.str;
-      default = {};
-    };
-    after = lib.mkOption {
-      description = "after scripts.";
-      type = types.attrsOf types.str;
-      default = {};
-    };
-  };
 };
 
 config = lib.mkIf dovecot2.enable {
   systemd.services.dovecot2 = {
     preStart = unlinesValues {
-      installMailDir = ''
-        # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
-        install -D -d -m 0771 \
-         -o ${dovecot2.mailUser} \
-         -g ${dovecot2.mailGroup} \
-         ${mailDir}
-      '';
-
-      installSieve = ''
-        rm -rf "${sieveDir}"
-        install -D -d -m 0755 -o root -g root \
-         "${sieveDir}/bin"
-      '' + unlinesAttrs (dir: sieves: ''
-        install -D -d -m 0755 -o root -g root \
-         ${sieveDir} ${sieveDir}/${dir}.d
-        '' + unlinesAttrs (name: text: ''
-          src=${pkgs.writeText "${name}.sieve" text}
-          dst="${sieveDir}/${dir}.d/${name}.sieve"
-          ln -fns "$src" "$dst"
-          ${pkgs.dovecot_pigeonhole}/bin/sievec "$dst"
-        '') sieves
-      ) dovecot2.sieves;
-
       installDomains =
         lib.optionalString openldap.enable ''
           # NOTE: make sure nslcd cache is in sync with the LDAP data
           systemctl restart nslcd
         '' + ''
+        # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
+        install -D -d -m 0771 \
+         -o "${dovecot2.user}" \
+         -g "${dovecot2.group}" \
+         ${stateDir}/mail
         install -D -d -m 1770 \
-         -o ${dovecot2.mailUser} \
-         -g ${domainGroup} \
-         ${mailDir}/${networking.domain} \
+         -o "${dovecot2.user}" \
+         -g "${domainGroup}" \
+         ${stateDir}/mail/${networking.domain} \
          ${stateDir}/control/${networking.domain} \
          ${stateDir}/index/${networking.domain}
 
@@ -131,14 +81,14 @@ config = lib.mkIf dovecot2.enable {
         #       rename acl.db.lock (own by new user)
         #       to     acl.db      (own by old user)
         install -D -d -m 0770 \
-         -o ${dovecot2.mailUser} \
-         -g ${domainGroup} \
+         -o "${dovecot2.user}" \
+         -g "${domainGroup}" \
          ${stateDir}/acl/${networking.domain}
 
         # NOTE: domainAliases point to the very same mailboxes as domain's.
         for domainAlias in ${unwords networking.domainAliases}
          do
-          ln -fns ${networking.domain} ${mailDir}/$domainAlias
+          ln -fns ${networking.domain} ${stateDir}/mail/$domainAlias
           ln -fns ${networking.domain} ${stateDir}/control/$domainAlias
           ln -fns ${networking.domain} ${stateDir}/index/$domainAlias
           ln -fns ${networking.domain} ${stateDir}/acl/$domainAlias
index aa3c32077017ded57dd43ba6db226637c2f7c50c..3daf41e366fe6222065adc3b4d6abbd50ff22535 100644 (file)
 { pkgs, lib, config, system, ... }:
-let inherit (builtins) toString toFile;
-    inherit (builtins.extraBuiltins) pass;
-    inherit (lib) types;
-    inherit (pkgs.lib) loadFile unlines unlinesAttrs unlinesValues unwords;
-    inherit (config) networking;
-    inherit (config.services) dovecot2 postfix openldap;
-    when        = x: y: if x == null then "" else y;
-    extSep      = postfix.recipientDelimiter;
-    dirSep      = extSep;
-    # NOTE: nixpkgs' dovecot2.stateDir is currently not exported
-    stateDir    = "/var/lib/dovecot";
-    mailDir     = "${stateDir}/mail";
-    sieveDir    = "${stateDir}/sieve";
-    authDir     = "${stateDir}/auth";
-    authUser    = dovecot2.mailUser;  # TODO: php_roundcube
-    authGroup   = dovecot2.mailGroup; # TODO: php_roundcube
-    escapeGroup = lib.stringAsChars (c: if "a"<=c && c<="z"
-                                        || "0"<=c && c<="9"
-                                        || c=="-"
-                                        then c else "_");
-    domainGroup = escapeGroup "${networking.domainBase}";
-    etc_dovecot = [
-      { target = "dovecot/${networking.domain}/dovecot-ldap.conf";
-        source = pkgs.writeText "dovecot-ldap.conf" ''
-          debug_level = 0
-
-          # LDAP database
-          uris = ldapi://
-          base = ou=posix,${openldap.domainSuffix}
-          scope = subtree
-          deref = never
-          # NOTE: sufficient for small systems and uses less resources.
-          blocking = no
-
-          # LDAP auth
-          sasl_bind = yes
-          sasl_mech = EXTERNAL
-          #dn = cn=admin,${openldap.domainSuffix}
-          #dnpass = useless with sasl_mech=EXTERNAL
-          auth_bind = no
-          #auth_bind_userdn = cn=%n,ou=accounts,ou=posix,dc=${openldap.domainSuffix}
-
-          # dovecot passdb query
-          # DOC: http://wiki2.dovecot.org/PasswordDatabase/ExtraFields
-          pass_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE))
-          # TODO: userdb_quota_rule=*:storage=
-          pass_attrs = userPassword=password,\
-                       uidNumber=userdb_uid,\
-                       gidNumber=userdb_gid,\
-                       mailGroupMember=userdb_mail_access_groups=${domainGroup},\
-                       quotaBytes=userdb_quota_rule=*:bytes=%{ldap:quotaBytes},\
-                       =user=%n@%d
-                       #homeDirectory=userdb_home
-          default_pass_scheme = CRYPT
-
-          # dovecot userdb query
-          # DOC: http://wiki2.dovecot.org/UserDatabase/ExtraFields
-          user_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE))
-          #user_filter = (&(objectClass=inetOrgPerson)(uid=%n))
-          #user_attrs = homeDirectory=home,uidNumber=uid,gidNumber=gid
-          #user_attrs = mailHomeDirectory=home,\
-          #              mailStorageDirectory=mail,\
-          #              mailUidNumber=uid,\
-          #              mailGidNumber=gid,\
-          #              mailQuota=quota_rule=*:bytes=%$
-
-          # doveadm user query
-          iterate_attrs = =user=%{ldap:uid}@${networking.domain}
-          iterate_filter = (&(objectClass=posixAccount)(mailEnabled=TRUE))
-        '';
-      }
+let
+  inherit (builtins) toString toFile;
+  inherit (builtins.extraBuiltins) pass;
+  inherit (lib) types;
+  inherit (pkgs.lib) loadFile unlines unlinesAttrs unlinesValues unwords;
+  inherit (config) networking;
+  inherit (config.services) dovecot2 postfix openldap;
+
+  # NOTE: nixpkgs' dovecot2.stateDir is currently not exported
+  stateDir    = "/var/lib/dovecot";
+
+  sieve_pipe_bin_dir = pkgs.buildEnv {
+    name = "sieve_pipe_bin_dir";
+    pathsToLink = [ "/bin" ];
+    paths = [
+      learn-spam
+      learn-ham
     ];
-    dovecot-virtual-pop3 = pkgs.writeTextFile {
-      name = "dovecot-virtual-pop3";
-      destination = "/pop3/INBOX/dovecot-virtual";
-      text = ''
-        All
-        All+*
-          all
-      '';
-    };
-
-    learn-spam = pkgs.writeShellScriptBin "learn-spam.sh" ''
-      exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/learner.sock learn_spam
+  };
+  learn-spam = pkgs.writeShellScriptBin "learn-spam.sh" ''
+    exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/learner.sock learn_spam
+  '';
+  learn-ham = pkgs.writeShellScriptBin "learn-ham.sh" ''
+    exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/learner.sock learn_ham
+  '';
+
+  dovecot-virtual = pkgs.buildEnv {
+    name = "dovecot-virtual";
+    pathsToLink = [ "/" ];
+    paths = [
+      dovecot-virtual-all
+      dovecot-virtual-recents
+    ];
+  };
+  dovecot-virtual-all = pkgs.writeTextFile {
+    name = "dovecot-virtual-all";
+    destination = "/All/dovecot-virtual";
+    text = ''
+      *
+        all
+    '';
+  };
+  dovecot-virtual-recents = pkgs.writeTextFile {
+    name = "dovecot-virtual-recents";
+    destination = "/Recents/dovecot-virtual";
+    text = ''
+      *
+        all younger 172800
     '';
-    learn-ham = pkgs.writeShellScriptBin "learn-ham.sh" ''
-      exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/learner.sock learn_ham
+  };
+  dovecot-virtual-pop3 = pkgs.writeTextFile {
+    name = "dovecot-virtual-pop3";
+    destination = "/pop3/INBOX/dovecot-virtual";
+    text = ''
+      All
+      All+*
+        all
     '';
-    sieve_pipe_bin_dir = pkgs.buildEnv {
-      name = "sieve_pipe_bin_dir";
-      pathsToLink = [ "/bin" ];
-      paths = [
-        learn-spam
-        learn-ham
-      ];
-    };
-    dovecot-virtual-all = pkgs.writeTextFile {
-      name = "dovecot-virtual-all";
-      destination = "/All/dovecot-virtual";
-      text = ''
-        *
-          all
-      '';
-    };
-    dovecot-virtual-recents = pkgs.writeTextFile {
-      name = "dovecot-virtual-recents";
-      destination = "/Recents/dovecot-virtual";
-      text = ''
-        *
-          all younger 172800
-      '';
-    };
-    dovecot-virtual = pkgs.buildEnv {
-      name = "dovecot-virtual";
-      pathsToLink = [ "/" ];
-      paths = [
-        dovecot-virtual-all
-        dovecot-virtual-recents
-      ];
-    };
+  };
 in
 {
   imports = [
     dovecot/autoconfig.nix
   ];
-  #services.postfix.mapFiles."transport-dovecot" =
-  #  toFile "transport-dovecot"
-  #   (unlines
-  #     (lib.mapAttrsToList
-  #       (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
-  #       dovecot2.domains));
   systemd.services.dovecot2 = {
     after = [
       "postfix.service"
       "openldap.service"
       "dovecot.${networking.domainBase}.key.pem-key.service"
     ];
-    restartTriggers = map (f: f.source) etc_dovecot;
   };
   deployment.keys = {
     "dovecot.${networking.domainBase}.key.pem" = {
@@ -147,108 +79,55 @@ in
       permissions = "0400"; # WARNING: not enforced when deployment.storeKeysOnMachine = true
     };
   };
-  environment.etc = etc_dovecot;
-  users.users."${dovecot2.mailUser}".isSystemUser = true; # Fix nixpkgs
+  #users.users."${dovecot2.mailUser}".isSystemUser = true; # Fix nixpkgs
   services.dovecot2 = {
     enable    = true;
-    mailUser  = "dovemail";
-    mailGroup = "dovemail";
     modules   = [
       pkgs.dovecot_pigeonhole
       pkgs.dovecot_fts_xapian
     ];
-    sieves = {
-      global = {
-        list = ''
-          require [ "date", "fileinto", "mailbox", "variables" ];
-
-          if currentdate :matches "year"  "*" { set "year"  "''${1}"; }
-          if currentdate :matches "month" "*" { set "month" "''${1}"; }
-
-          if exists "List-ID" {
-            if header :matches "List-ID" "*<*.*.*.*>*" {
-              set "list"   "''${2}";
-              set "domain" "''${4}";
-            }
-            elsif header :matches "List-ID" "*<*.*.*>*" {
-              set "list"   "''${2}";
-              set "domain" "''${3}";
-            }
-            fileinto :create "Listes+''${domain}+''${list}+''${year}+''${month}";
-            stop;
-           }
-        '';
-        spam = ''
-          require [ "imap4flags" ];
-
-          if header :contains "X-Spam-Level" "***" {
-            addflag "Junk";
-          }
-        '';
-        extension = ''
-          require
-           [ "envelope"
-           , "fileinto"
-           , "mailbox"
-           , "subaddress"
-           , "variables"
-           ];
-          if envelope :matches :detail "TO" "*" {
-            set "extension" "''${1}";
-          }
-          if not string :is "''${extension}" "" {
-            fileinto :create "INBOX+''${extension}";
-            stop;
-          }
-        '';
-        spam-or-ham = ''
-          require ["vnd.dovecot.pipe", "copy", "imapsieve", "variables", "imap4flags", "environment"];
-
-          if environment :is "imap.changedflags" "Junk" {
-            if hasflag :is "Junk" {
-              pipe :copy :try "learn-spam.sh";
-            } elsif not hasflag :is "Junk" {
-              pipe :copy :try "learn-ham.sh";
-            }
-          }
-        '';
-        report-spam = ''
-          require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
-
-          if environment :matches "imap.user" "*" {
-            set "username" "''${1}";
-          }
-
-          pipe :copy :try "learn-spam.sh" [ "''${username}" ];
-        '';
-        report-ham = ''
-          require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
-
-          if environment :matches "imap.mailbox" "*" {
-            set "mailbox" "''${1}";
-          }
-
-          if string "''${mailbox}" "Trash" {
-            stop;
-          }
+    enablePAM = false;
+    enableImap = true;
+    enableLmtp = true;
+    enablePop3 = true;
+    protocols = [ "sieve" ];
+    mailLocation = "sdbox:${stateDir}/mail/%d/%n/mail.d:UTF-8:CONTROL=${stateDir}/control/%d/%n:INDEX=${stateDir}/index/%d/%n";
+    createMailUser = false;
+    mailUser = "";
+    mailGroup = "";
+    sslServerCert = null;
+    sieveScripts = {
+      global = dovecot/sieve/global;
+    };
+    extraConfig = ''
+      auth_verbose = no
+      auth_debug = no
+      mail_debug = yes
+      verbose_ssl = no
+      log_timestamp = "%Y-%m-%d %H:%M:%S "
 
-          if environment :matches "imap.user" "*" {
-            set "username" "''${1}";
-          }
+      ssl = required
+      ssl_dh = <${../../../sec/openssl/dh.pem}
+      ssl_cipher_list = HIGH:!LOW:!SSLv2:!EXP:!aNULL
+      ssl_cert = <${loadFile (../../../sec + "/openssl/${networking.domainBase}/cert.self-signed.pem")}
+      ssl_key = </run/keys/dovecot.${networking.domainBase}.key.pem
+      #ssl_ca = <''${caPath}
+      #ssl_verify_client_cert = yes
 
-          pipe :copy :try "learn-ham.sh" [ "''${username}" ];
-        '';
-      };
-    };
-    configFile = toString ( pkgs.writeText "dovecot.conf" ''
-      auth_debug  = yes
-      mail_debug  = yes
-      verbose_ssl = yes
+      listen = *
+      # If needed, may be overrided by userdb_mail
+      mail_home = ${stateDir}/mail/%d/%n
+      # Read multiple mails in parallel, improves performance
+      mail_prefetch_count = 20
+      # Default VSZ (virtual memory size) limit for service processes. This is mainly
+      # intended to catch and kill processes that leak memory before they eat up everything.
+      # Increased for fts_xapian.
+      default_vsz_limit = 1G
 
       passdb {
         driver = ldap
-        args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf
-        default_fields = userdb_mail_access_groups=${domainGroup}
+        args = ${loadFile (dovecot/ldap + "/${networking.domainBase}.conf")}
+        default_fields = userdb_mail_access_groups=${networking.domainBase}
         override_fields =
       }
       userdb {
@@ -257,25 +136,26 @@ in
       userdb {
         # NOTE: this userdb is only used by lda.
         driver = ldap
-        args = /etc/dovecot/${networking.domain}/dovecot-ldap.conf
-        default_fields = mail_access_groups=${domainGroup}
+        args = ${loadFile (dovecot/ldap + "/${networking.domainBase}.conf")}
+        default_fields = mail_access_groups=${networking.domainBase}
         override_fields =
         skip = found
       }
+      service auth {
+        # FIXME: may be user=dovecot-auth with LDAP?
+        user = root
+        unix_listener auth-userdb {
+          user  = ${dovecot2.user}
+          group = ${dovecot2.group}
+          mode  = 0660
+        }
+        unix_listener /var/lib/postfix/queue/private/auth {
+          user  = ${postfix.user}
+          group = ${postfix.group}
+          mode  = 0660
+        }
+      }
       auth_cache_verify_password_with_worker = yes
-      #passdb {
-      #  driver = passwd-file
-      #  args   = scheme=crypt username_format=%n ${authDir}/%d/passwd
-      #}
-      #userdb {
-      #  # NOTE: this userdb is only used by lda.
-      #  driver = passwd-file
-      #  args = username_format=%n ${authDir}/%d/passwd
-      #  #default_fields = home=${mailDir}/%d/%n
-      #  skip = found
-      #}
-      # If needed, may be overrided by userdb_mail
-      mail_home = ${mailDir}/%d/%n
       # NOTE: if needed, may be overrided by userdb_mail
       # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc.
       # SEE: http://wiki2.dovecot.org/Quota/FS
@@ -283,29 +163,33 @@ in
       # NOTE: postfix does not supply a client cert.
       auth_ssl_require_client_cert = no
       #auth_ssl_username_from_cert = yes
-      auth_verbose = yes
       # NOTE: lowercase the username, help with LDAP?
       auth_username_format = %Lu
-      default_internal_user = ${dovecot2.user}
-      default_internal_group = ${dovecot2.group}
-      disable_plaintext_auth = yes
       # NOTE: sync with LDAP's data.
       first_valid_uid = 1000
-      lda_mailbox_autocreate = yes
-      lda_mailbox_autosubscribe = yes
-      listen = *
-      log_timestamp = "%Y-%m-%d %H:%M:%S "
-      mail_location = sdbox:/var/lib/dovecot/mail/%d/%n/mail.d:UTF-8:CONTROL=/var/lib/dovecot/control/%d/%n:INDEX=/var/lib/dovecot/index/%d/%n
-      # No dirty syncs while I'm using neomutt directly on the Maildirs
-      #maildir_very_dirty_syncs = yes
-      #maildir_copy_with_hardlinks = yes
+      disable_plaintext_auth = yes
+
+      #passdb {
+      #  driver = passwd-file
+      #  args   = scheme=crypt username_format=%n ${stateDir}/passwd/%d
+      #}
+      #userdb {
+      #  # NOTE: this userdb is only used by LDA.
+      #  driver = passwd-file
+      #  args = username_format=%n ${stateDir}/passwd/%d
+      #  #default_fields = home=${stateDir}/mail/%d/%n
+      #  skip = found
+      #}
+
+      # No dirty syncs for maildir: locations which may be used directly by neomutt
+      maildir_very_dirty_syncs = no
       namespace Inbox {
         type      = private
         inbox     = yes
         hidden    = no
         list      = yes
         prefix    =
-        separator = ${dirSep}
+        separator = +
       }
       namespace Shared {
         type = shared
@@ -316,39 +200,34 @@ in
         # NOTE: %var expands to the logged in user's variable, while
         #       %%var expands to the other users' variables.
         # NOTE: INDEX and CONTROL are shared, INDEXPVT is not.
-        location = sdbox:${mailDir}/%%d/%%n/mail.d:UTF-8:CONTROL=${stateDir}/control/%%d/%%n/Shared:INDEX=${stateDir}/index/%%d/%%n/Shared:INDEXPVT=${stateDir}/index/%d/%n/Shared/%%n
+        location = sdbox:${stateDir}/mail/%%d/%%n/${stateDir}/mail.d:UTF-8:CONTROL=${stateDir}/control/%%d/%%n/Shared:INDEX=${stateDir}/index/%%d/%%n/Shared:INDEXPVT=${stateDir}/index/%d/%n/Shared/%%n
         prefix = Partages+%%n+
-        separator = ${dirSep}
+        separator = +
         subscriptions = yes
       }
+      mail_plugins = $mail_plugins virtual
       namespace Virtual {
         prefix = Virtual+
-        separator = ${dirSep}
+        separator = +
         hidden = no
         list = yes
         subscriptions = no
         location = virtual:${dovecot-virtual}:UTF-8:INDEX=${stateDir}/index/%d/%n/virtual
       }
-      # Default VSZ (virtual memory size) limit for service processes. This is mainly
-      # intended to catch and kill processes that leak memory before they eat up everything.
-      # Increased for fts_xapian.
-      default_vsz_limit = 1G
-      mail_plugins = $mail_plugins acl quota virtual fts fts_xapian
-      #mail_uid = ${dovecot2.mailUser}
-      #mail_gid = ${dovecot2.mailGroup}
-        # NOTE: each user has a dedicated (uid,gid) pair
-      #mail_privileged_group = mail
-      #mail_access_groups =
-      plugin {
-        plugin = fts fts_xapian
 
+      mail_plugins = $mail_plugins acl
+      plugin {
         acl = vfile:/etc/dovecot/acl/global.d
         acl_anyone = allow
         # NOTE: to let users LIST mailboxes shared by other users,
         #       Dovecot needs a shared mailbox dictionary.
         # FIXME: %d not working with userdb ldap
         acl_shared_dict = file:${stateDir}/acl/%d/acl.db
+      }
 
+      mail_plugins = $mail_plugins fts fts_xapian
+      plugin {
+        plugin = fts fts_xapian
         fts = xapian
         fts_autoindex = yes
         fts_autoindex_exclude = \Junk
@@ -358,7 +237,10 @@ in
         # keywords created for fields (To, Cc, ...) are between is 2 and 20 chars long.
         # Full words are also added by default.
         fts_xapian = partial=2 full=20 verbose=0
+      }
 
+      mail_plugins = $mail_plugins quota
+      plugin {
         quota = maildir:User quota
         quota_rule = *:storage=256M
         quota_rule2 = Trash:storage=+64M
@@ -367,73 +249,62 @@ in
         quota_warning  = storage=95%% quota-warning 95 %u
         quota_warning2 = storage=80%% quota-warning 80 %u
         quota_warning3 = -storage=100%% quota-warning below %u
+      }
 
+      protocol lda {
+        auth_socket_path = /var/run/dovecot/auth-userdb
+        hostname         = ${networking.domain}
+        info_log_path    =
+        log_path         =
+        mail_plugins     = $mail_plugins sieve
+        postmaster_address = postmaster+dovecot+lda@${networking.domain}
+        syslog_facility = mail
+      }
+      lda_mailbox_autocreate = yes
+      lda_mailbox_autosubscribe = yes
+
+      protocol lmtp {
+        #info_log_path = /tmp/dovecot-lmtp.log
+        mail_plugins = $mail_plugins sieve
+        postmaster_address = postmaster+dovecot+lmtp@${networking.domain}
+      }
+      service lmtp {
+        #executable = lmtp -L
+        process_min_avail = ${toString config.nix.maxJobs}
+        unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
+          user  = ${postfix.user}
+          group = ${postfix.group}
+          mode  = 0600
+        }
+        #user = mail
+      }
+      plugin {
         # Let extension.sieve do the handling of the detail
         #lmtp_save_to_detail_mailbox = yes
-        recipient_delimiter = ${extSep}
-
-        #sieve_default = file:${mailDir}/%u/default.sieve
-        #sieve_default_name = default
-        sieve_plugins = sieve_imapsieve sieve_extprograms
-        sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
-        #sieve_extensions = +editheader
-        sieve = file:~/sieve.d;active=~/sieve
-        sieve_after = ${sieveDir}/after.d/
-        sieve_before = ${sieveDir}/before.d/
-        sieve_pipe_bin_dir = ${sieve_pipe_bin_dir}/bin
-        sieve_global = ${sieveDir}/global.d/
-        sieve_max_script_size = 1M
-        sieve_quota_max_scripts = 0
-        sieve_quota_max_storage = 10M
-        #sieve_spamtest_max_value = 10
-        #sieve_spamtest_status_header = X-Spam-Score
-        #sieve_spamtest_status_type = strlen
-        sieve_user_log = /var/log/dovecot/%d/sieve.%n.log
-        # Enables support for user Sieve scripts in IMAP
-        #imapsieve_url = sieve://mail.${networking.domain}:4190
-
-        # When a flag changes, spam or ham according to the \Junk or \NonJunk flags
-        imapsieve_mailbox1_name = *
-        imapsieve_mailbox1_causes = FLAG
-        imapsieve_mailbox1_before = file:${sieveDir}/global.d/spam-or-ham.sieve
-
-        # From elsewhere to Junk folder
-        imapsieve_mailbox2_name = Pourriel
-        imapsieve_mailbox2_causes = COPY APPEND
-        imapsieve_mailbox2_before = file:${sieveDir}/global.d/report-spam.sieve
-
-        # From Junk folder to elsewhere
-        imapsieve_mailbox3_name = *
-        imapsieve_mailbox3_from = Pourriel
-        imapsieve_mailbox3_causes = COPY
-        imapsieve_mailbox3_before = file:${sieveDir}/global.d/report-ham.sieve
+        recipient_delimiter = ${postfix.recipientDelimiter}
       }
-      # If you have Dovecot v2.2.8+ you may get a significant performance improvement with fetch-headers:
-      imapc_features = $imapc_features fetch-headers
-      # Read multiple mails in parallel, improves performance
-      mail_prefetch_count = 20
-      service quota-warning {
-        executable = script ${
-          pkgs.writeScript "quota-warning" ''
-            #!/bin/sh -eu
-            PERCENT=$1
-            USER=$2
-            cat << EOF | ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -d $USER -o
-            "plugin/quota=maildir:User quota:noenforcing"
-            From: postmaster@${networking.domain}
-            Subject: [WARNING] your mailbox is now $PERCENT% full.
 
-            Please remove some mails to make room for new ones.
-            EOF
-          ''
-        }
-        # use some unprivileged user for executing the quota warnings
-        user = ${dovecot2.user}
-        unix_listener quota-warning {
+      service imap {
+        # Most of the memory goes to mmap()ing files.
+        # You may need to increase this limit if you have huge mailboxes.
+        #vsz_limit =
+        process_limit = 1024
+      }
+      service imap-login {
+        #inet_listener imap {
+        #  address = 127.0.0.1
+        #  port    = 143
+        #  ssl     = no
+        #}
+        inet_listener imaps {
+          port = 993
+          ssl  = yes
         }
       }
       # Store METADATA information within user's HOME directory
       mail_attribute_dict = file:%h/dovecot-attributes
+      # If you have Dovecot v2.2.8+ you may get a significant performance improvement with fetch-headers:
+      imapc_features = $imapc_features fetch-headers
       protocol imap {
         #mail_max_userip_connections = 10
         mail_plugins = $mail_plugins imap_acl imap_quota imap_sieve virtual
@@ -516,39 +387,40 @@ in
           }
         }
       }
-      protocol lda {
-        auth_socket_path = /var/run/dovecot/auth-userdb
-        hostname         = ${networking.domain}
-        info_log_path    =
-        log_path         =
-        mail_plugins     = $mail_plugins sieve
-        postmaster_address = postmaster${extSep}dovecot${extSep}lda@${networking.domain}
-        syslog_facility = mail
-      }
-      protocol lmtp {
-        #info_log_path = /tmp/dovecot-lmtp.log
-        mail_plugins = $mail_plugins sieve
-        postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${networking.domain}
-      }
-      protocol pop3 {
-        mail_plugins = $mail_plugins virtual
-        #mail_max_userip_connections = 10
-        # Virtual namespace for the virtual INBOX.
-        # Use a global directory for dovecot-virtual files.
-        #namespace Inbox {
-        #  hidden    = yes
-        #  list      = no
-        #  location  = virtual:''${dovecot-virtual-pop3}/pop3:INDEX=${stateDir}/index/%d/%n/virtual/pop3:LAYOUT=fs
-        #  prefix    = pop3+
-        #}
-        pop3_client_workarounds =
-        pop3_fast_size_lookups = yes
-        pop3_lock_session = yes
-        pop3_no_flag_updates = yes
-        # Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs.
-        pop3_uidl_format = %g
+      plugin {
+        #sieve_default = file:${stateDir}/mail/%u/default.sieve
+        #sieve_default_name = default
+        sieve_plugins = sieve_imapsieve sieve_extprograms
+        sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
+        #sieve_extensions = +editheader
+        sieve = file:~/sieve.d;active=~/sieve
+        sieve_pipe_bin_dir = ${sieve_pipe_bin_dir}/bin
+        sieve_max_script_size = 1M
+        sieve_quota_max_scripts = 0
+        sieve_quota_max_storage = 10M
+        #sieve_spamtest_max_value = 10
+        #sieve_spamtest_status_header = X-Spam-Score
+        #sieve_spamtest_status_type = strlen
+        sieve_user_log = /var/log/dovecot/%d/sieve.%n.log
+        # Enables support for user Sieve scripts in IMAP
+        #imapsieve_url = sieve://mail.${networking.domain}:4190
+
+        # When a flag changes, spam or ham according to the \Junk or \NonJunk flags
+        imapsieve_mailbox1_name = *
+        imapsieve_mailbox1_causes = FLAG
+        imapsieve_mailbox1_before = file:${stateDir}/sieve/global/spam-or-ham.sieve
+
+        # From elsewhere to Junk folder
+        imapsieve_mailbox2_name = Pourriel
+        imapsieve_mailbox2_causes = COPY APPEND
+        imapsieve_mailbox2_before = file:${stateDir}/sieve/global/report-spam.sieve
+
+        # From Junk folder to elsewhere
+        imapsieve_mailbox3_name = *
+        imapsieve_mailbox3_from = Pourriel
+        imapsieve_mailbox3_causes = COPY
+        imapsieve_mailbox3_before = file:${stateDir}/sieve/global/report-ham.sieve
       }
-      # DOC: https://wiki2.dovecot.org/Pigeonhole/ManageSieve/Configuration
       protocol sieve {
         # Maximum number of ManageSieve connections allowed for a user from each IP address.
         # NOTE: The username is compared case-sensitively.
@@ -570,57 +442,6 @@ in
         #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 ihave
       }
-      protocols = imap lmtp pop3 sieve
-      service lmtp {
-        #executable = lmtp -L
-        process_min_avail = ${toString config.nix.maxJobs}
-        unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
-          user  = ${postfix.user}
-          group = ${postfix.group}
-          mode  = 0600
-        }
-        #user = mail
-      }
-      service auth {
-        # FIXME: may be user=dovecot-auth with LDAP?
-        user = root
-        unix_listener auth-userdb {
-          user  = ${dovecot2.user}
-          group = ${dovecot2.group}
-          mode  = 0660
-        }
-        unix_listener /var/lib/postfix/queue/private/auth {
-          user  = ${postfix.user}
-          group = ${postfix.group}
-          mode  = 0660
-        }
-      }
-      service imap {
-        # Most of the memory goes to mmap()ing files.
-        # You may need to increase this limit if you have huge mailboxes.
-        #vsz_limit =
-        process_limit = 1024
-      }
-      service imap-login {
-        #inet_listener imap {
-        #  address = 127.0.0.1
-        #  port    = 143
-        #  ssl     = no
-        #}
-        inet_listener imaps {
-          port = 993
-          ssl  = yes
-        }
-      }
-      service pop3 {
-        process_limit = 1024
-      }
-      service pop3-login {
-        inet_listener pop3s {
-          port = 995
-          ssl  = yes
-        }
-      }
       service managesieve-login {
         inet_listener sieve {
           port = 4190
@@ -638,23 +459,71 @@ in
         # If you set service_count=0, you probably need to grow this.
         #vsz_limit = 64M
       }
-      ssl = required
-      ssl_dh = <${../../../sec/openssl/dh.pem}
-      ssl_cipher_list = HIGH:!LOW:!SSLv2:!EXP:!aNULL
-      ssl_cert = <${loadFile (../../../sec + "/openssl/${networking.domainBase}/cert.self-signed.pem")}
-      ssl_key = </run/keys/dovecot.${networking.domainBase}.key.pem
-      #ssl_ca = <''${caPath}
-      #ssl_verify_client_cert = yes
-    '' );
-      #${lib.concatMapStringsSep "\n"
-      #    (dom: ''
-      #      local_name mail.${dom} {
-      #        #ssl_ca   = <''${caPath}
-      #        ssl_cert = <${x509.cert dom}
-      #        ssl_key  = <${x509.key dom}
-      #        }
-      #    '')
-      #    dovecot2.domains
-      #}
+
+      service quota-warning {
+        executable = script ${
+          pkgs.writeScript "quota-warning" ''
+            #!/bin/sh -eu
+            PERCENT=$1
+            USER=$2
+            cat << EOF | ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -d $USER -o
+            "plugin/quota=maildir:User quota:noenforcing"
+            From: postmaster@${networking.domain}
+            Subject: [WARNING] your mailbox is now $PERCENT% full.
+
+            Please remove some mails to make room for new ones.
+            EOF
+          ''
+        }
+        # use some unprivileged user for executing the quota warnings
+        user = ${dovecot2.user}
+        unix_listener quota-warning {
+        }
+      }
+
+      protocol pop3 {
+        mail_plugins = $mail_plugins virtual
+        #mail_max_userip_connections = 10
+        # Virtual namespace for the virtual INBOX.
+        # Use a global directory for dovecot-virtual files.
+        #namespace Inbox {
+        #  hidden    = yes
+        #  list      = no
+        #  location  = virtual:''${dovecot-virtual-pop3}/pop3:INDEX=${stateDir}/index/%d/%n/virtual/pop3:LAYOUT=fs
+        #  prefix    = pop3+
+        #}
+        pop3_client_workarounds =
+        pop3_fast_size_lookups = yes
+        pop3_lock_session = yes
+        pop3_no_flag_updates = yes
+        # Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs.
+        pop3_uidl_format = %g
+      }
+      service pop3 {
+        process_limit = 1024
+      }
+      service pop3-login {
+        inet_listener pop3s {
+          port = 995
+          ssl  = yes
+        }
+      }
+    '';
+    #${lib.concatMapStringsSep "\n"
+    #    (dom: ''
+    #      local_name mail.${dom} {
+    #        #ssl_ca   = <''${caPath}
+    #        ssl_cert = <${x509.cert dom}
+    #        ssl_key  = <${x509.key dom}
+    #        }
+    #    '')
+    #    dovecot2.domains
+    #}
   };
+  #services.postfix.mapFiles."transport-dovecot" =
+  #  toFile "transport-dovecot"
+  #   (unlines
+  #     (lib.mapAttrsToList
+  #       (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
+  #       dovecot2.domains));
 }
diff --git a/servers/mermet/dovecot/ldap/sourcephile.conf b/servers/mermet/dovecot/ldap/sourcephile.conf
new file mode 100644 (file)
index 0000000..ff471d3
--- /dev/null
@@ -0,0 +1,41 @@
+debug_level = 0
+
+# LDAP database 
+uris = ldapi://
+base = ou=posix,dc=sourcephile,dc=fr
+scope = subtree
+deref = never
+# NOTE: sufficient for small systems and uses less resources.
+blocking = no
+
+# LDAP auth
+sasl_bind = yes
+sasl_mech = EXTERNAL
+#dn = cn=admin,dc=sourcephile,dc=fr
+#dnpass = useless with sasl_mech=EXTERNAL
+auth_bind = no
+#auth_bind_userdn = cn=%n,ou=accounts,ou=posix,dc=dc=sourcephile,dc=fr
+
+# dovecot passdb query
+pass_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE))
+pass_attrs = userPassword=password,\
+             uidNumber=userdb_uid,\
+             gidNumber=userdb_gid,\
+             mailStorageDirectory=userdb_mail,\
+             mailGroupMember=userdb_mail_access_groups=sourcephile,\
+             quotaBytes=userdb_quota_rule=*:bytes=%{ldap:quotaBytes},\
+             =user=%n@%d
+             #homeDirectory=userdb_home
+default_pass_scheme = CRYPT
+
+# dovecot userdb query
+# For dovecot-lda
+user_filter = (&(objectClass=posixAccount)(uid=%n)(mailEnabled=TRUE))
+user_attrs = mailStorageDirectory=mail,\
+             uidNumber=uid,\
+             gidNumber=gid,\
+             mailQuota=quota_rule=*:bytes=%{ldap:quotaBytes}
+
+# doveadm user query
+iterate_attrs = =user=%{ldap:uid}@sourcephile.fr
+iterate_filter = (&(objectClass=posixAccount)(mailEnabled=TRUE))
diff --git a/servers/mermet/dovecot/sieve/global/extension.sieve b/servers/mermet/dovecot/sieve/global/extension.sieve
new file mode 100644 (file)
index 0000000..6ecdc4e
--- /dev/null
@@ -0,0 +1,8 @@
+require [ "envelope" , "fileinto" , "mailbox" , "subaddress" , "variables" ];
+
+if envelope :matches :detail "TO" "*" { set "extension" "${1}"; }
+
+if not string :is "${extension}" "" {
+  fileinto :create "INBOX+${extension}";
+  stop;
+}
diff --git a/servers/mermet/dovecot/sieve/global/flag-spam.sieve b/servers/mermet/dovecot/sieve/global/flag-spam.sieve
new file mode 100644 (file)
index 0000000..cd1b39e
--- /dev/null
@@ -0,0 +1,5 @@
+require [ "imap4flags" ];
+
+if header :contains "X-Spam-Level" "***" {
+  addflag "Junk";
+}
diff --git a/servers/mermet/dovecot/sieve/global/list.sieve b/servers/mermet/dovecot/sieve/global/list.sieve
new file mode 100644 (file)
index 0000000..529b61c
--- /dev/null
@@ -0,0 +1,17 @@
+require [ "date", "fileinto", "mailbox", "variables" ];
+
+if currentdate :matches "year"  "*" { set "year"  "${1}"; }
+if currentdate :matches "month" "*" { set "month" "${1}"; }
+
+if exists "List-ID" {
+  if header :matches "List-ID" "*<*.*.*.*>*" {
+    set "list"   "${2}";
+    set "domain" "${4}";
+  }
+  elsif header :matches "List-ID" "*<*.*.*>*" {
+    set "list"   "${2}";
+    set "domain" "${3}";
+  }
+  fileinto :create "Listes+${domain}+${list}+${year}+${month}";
+  stop;
+}
diff --git a/servers/mermet/dovecot/sieve/global/report-ham.sieve b/servers/mermet/dovecot/sieve/global/report-ham.sieve
new file mode 100644 (file)
index 0000000..a96d69f
--- /dev/null
@@ -0,0 +1,9 @@
+require [ "copy", "environment", "imapsieve", "variables", "vnd.dovecot.pipe" ];
+
+if environment :matches "imap.mailbox" "*" { set "mailbox" "${1}"; }
+if string "${mailbox}" "Trash" { stop; }
+if string "${mailbox}" "Corbeille" { stop; }
+
+if environment :matches "imap.user" "*" { set "username" "${1}"; }
+
+pipe :copy :try "learn-ham.sh" [ "${username}" ];
diff --git a/servers/mermet/dovecot/sieve/global/report-spam.sieve b/servers/mermet/dovecot/sieve/global/report-spam.sieve
new file mode 100644 (file)
index 0000000..5f11a82
--- /dev/null
@@ -0,0 +1,5 @@
+require [ "copy", "imapsieve", "environment", "variables", "vnd.dovecot.pipe" ];
+
+if environment :matches "imap.user" "*" { set "username" "${1}"; }
+
+pipe :copy :try "learn-spam.sh" [ "${username}" ];
diff --git a/servers/mermet/dovecot/sieve/global/spam-or-ham.sieve b/servers/mermet/dovecot/sieve/global/spam-or-ham.sieve
new file mode 100644 (file)
index 0000000..6246036
--- /dev/null
@@ -0,0 +1,10 @@
+require [ "copy", "environment", "imap4flags", "imapsieve", "variables", "vnd.dovecot.pipe" ];
+
+if environment :is "imap.changedflags" "Junk" {
+  if environment :matches "imap.user" "*" { set "username" "${1}"; }
+  if hasflag :is "Junk" {
+    pipe :copy :try "learn-spam.sh" [ "${username}" ];
+  } elsif not hasflag :is "Junk" {
+    pipe :copy :try "learn-ham.sh" [ "${username}" ];
+  }
+}
index d45c31e66583c70f97548bb9bfe3f9a8eac212e0..6df9495e37a4be36488ed606aac0d17a68fe154e 100644 (file)
@@ -58,7 +58,7 @@ in
   config = lib.mkIf config.users.ldap.enable {
     services.openldap = {
       databases = {
-        # echo "$(nixops show-option mermet -d production services.openldap.databases."dc=sourcephile,dc=fr".data)"
+        # DEBUG: echo "$(nixops show-option mermet -d production services.openldap.databases."dc=sourcephile,dc=fr".data)"
         "${domainSuffix}" = {
           #
           #
@@ -66,8 +66,10 @@ in
           #
           #
           resetData = true;
+
+          # DEBUG: sudo ldapsearch -LLL -H ldapi:// -D cn=admin,cn=config -Y EXTERNAL -b 'olcDatabase={1}mdb,cn=config' -s sub
+          # WARNING: newlines matter
           conf = ''
-            # sudo ldapsearch -LLL -H ldapi:// -D cn=admin,cn=config -Y EXTERNAL -b 'olcDatabase={1}mdb,cn=config' -s sub
             dn: olcBackend={1}mdb,cn=config
             objectClass: olcBackendConfig
 
@@ -147,14 +149,22 @@ in
             memberUid: sevy
 
           ''
-          + lib.concatMapStrings posixAccount [
-            { uid="julm";
-              uidNumber=users.julm.uid;
-              gidNumber=groups.julm.gid;
-              cn="Julien Moutinho"; sn="julm";
-              mailAlias = ["julien.moutinho"];
+          + lib.concatMapStrings posixAccount [ rec
+            { uid = "julm";
+              cn = "Julien Moutinho";
+              sn = uid;
+              uidNumber = users.julm.uid;
+              gidNumber = groups.julm.gid;
+              mailAlias = [ "julien.moutinho" ];
               userPassword = pass-chomp "members/julm/mail/hashedPassword";
-              mailStorageDirectory = "maildir:/home/julm/Maildir/sourcephile.fr:UTF-8:LAYOUT=maildir++";
+              mailStorageDirectory =
+                let stateDir = "/var/lib/dovecot";
+                    d="sourcephile.fr";
+                in
+                # I'm personnaly using "maildir:" instead of "sdbox:" to be able to use a local (neo)mutt on it,
+                # bypassing IMAP because (neo)mutt support of IMAP is very bad
+                # (can't even have a decent $folder_format (with %n or %m) working).
+                "maildir:${stateDir}/mail/${d}/${uid}/mail.d:LAYOUT=maildir++:UTF-8:CONTROL=${stateDir}/control/${d}/${uid}:INDEX=${stateDir}/index/${d}/${uid}";
             }
             #{ uid="sevy"; uidNumber=10001; cn="Séverine Popek"; sn="sévy";
             #  mailAlias = ["severine.popek" "ouais-ouais"]; }