{ config, lib, pkgs, ... }: with lib; let cfg = config.local; in { options.local.mailHost = with types; { enable = mkEnableOption "mailbox host service"; security.acme.defaults.dnsProvider = "gandiv5"; mdaListen = mkOption { type = str; }; saslPort = mkOption { type = port; }; lmtpPort = mkOption { type = port; }; }; config = let imapHostname = cfg.domains.imap.main; in mkIf cfg.mailHost.enable { services.dovecot2 = let cert = config.security.acme.certs.${imapHostname}.directory; in { enable = true; enablePAM = false; enableLmtp = true; sslServerKey = "${cert}/key.pem"; sslServerCert = "${cert}/fullchain.pem"; modules = [ pkgs.dovecot_pigeonhole ]; mailUser = "vmail"; mailGroup = "vmail"; mailLocation = "maildir:~/mail"; mailPlugins.perProtocol.lmtp.enable = [ "sieve" ]; extraConfig = let inherit (config.networking) domain; # https://dovecot.org/list/dovecot/2019-March/115250.html # Otra solución posible (https://serverfault.com/a/1062274/980378): # auth_username_format = %{if;%d;eq;${domain};%Ln;%Lu} localEntry = canonical: username: '' ${username}:::::::user=${canonical} nopassword userdb_user=${canonical} ''; localEntries = concatStrings (flatten (mapAttrsToList (canonical: user: map (localEntry canonical) ([ canonical ] ++ user.hardAliases)) cfg.users)); localMailboxes = pkgs.writeText "local-mailboxes" localEntries; vmailPath = "/var/lib/vmail/%{if;%d;ne;;%Ld;${domain}}"; in '' auth_mechanisms = plain login external # TODO: los defaults de nixpkgs dejan los sockets bajo # /run/dovecot2 con demasiados permisos rwx, arreglar service auth { inet_listener mta-sasl { port = ${toString cfg.mailHost.saslPort} address = ${cfg.mailHost.mdaListen} } } service lmtp { inet_listener mta-lmtp { port = ${toString cfg.mailHost.lmtpPort} address = ${cfg.mailHost.mdaListen} } } # Esto enfuerza user@domain.tld auth_username_format = %{if;%Ld;eq;${domain};%Ln;%{if;%d;ne;;%Lu;%Ln@invalid}} # FIXME: Esta cadena de passdbs hace que 'doveadm user lookup' # falle para usuarios locales, pero todo lo demás sirve. Parece # ser debido a que pam no puede enumerar. passdb { driver = passwd-file args = username_format=%Ln ${vmailPath}/passwd } passdb { driver = passwd-file args = ${localMailboxes} # Esta es una forma de determinar si se encontró el usuario en # el passwd-file por medio de nopassword sin realmente # autenticarlo. Cuidado con result_success, porque si eso se # configura mal se permite inicio de sesión con cualquier # contraseña (!!!). result_success = continue result_failure = return-fail result_internalfail = return-fail username_filter = !*@* } passdb { driver = pam args = dovecot2 username_filter = !*@* #TODO: algo como 'override_fields = allow_nets=...' } userdb { driver = passwd-file args = username_format=%Ln ${vmailPath}/passwd override_fields = uid=vmail gid=vmail home=${vmailPath}/home/%Ln } userdb { driver = passwd-file args = ${localMailboxes} result_success = continue-ok result_internalfail = return-fail skip = found } userdb { driver = passwd args = blocking=no skip = notfound } ''; }; security = { # Necesario debido a 'enablePAM = false' pam.services.dovecot2 = { }; acme.certs.${imapHostname} = { inherit (config.services.dovecot2) group; }; }; users = { users.${config.services.dovecot2.mailUser}.uid = 995; groups.${config.services.dovecot2.mailGroup}.gid = 993; }; networking.firewall.allowedTCPPorts = [ 143 587 993 ]; local.certs.imap.enable = true; }; }