{ config, lib, pkgs, environment, ... }: with lib; let cfg = config.fudo.mail-server; state-directory = "${cfg.state-directory}/dovecot"; pipe-bin = pkgs.stdenv.mkDerivation { name = "pipe_bin"; src = ./dovecot/pipe_bin; buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ]; buildCommand = '' mkdir -p $out/pipe/bin cp $src/* $out/pipe/bin/ chmod a+x $out/pipe/bin/* patchShebangs $out/pipe/bin for file in $out/pipe/bin/*; do wrapProgram $file \ --set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin" done ''; }; ldap-conf = filename: config: let ssl-config = if config.ca == null then '' tls = no tls_require_cert = try '' else '' tls_ca_cert_file = ${config.ca} tls = yes tls_require_cert = try ''; in pkgs.writeText filename '' uris = ${concatStringsSep " " config.server-urls} ldap_version = 3 dn = ${config.reader-dn} dnpass = ${config.reader-passwd} auth_bind = yes auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org base = dc=fudo,dc=org ${ssl-config} ''; ldap-passwd-entry = ldap-config: '' passdb { driver = ldap args = ${ldap-conf "ldap-passdb.conf" ldap-config} } ''; ldapOpts = { options = with types; { ca = mkOption { type = nullOr str; description = "The path to the CA cert used to sign the LDAP server certificate."; default = null; }; server-urls = mkOption { type = listOf str; description = "A list of LDAP server URLs used for authentication."; }; reader-dn = mkOption { type = str; description = '' DN to use for reading user information. Needs access to homeDirectory, uidNumber, gidNumber, and uid, but not password attributes. ''; }; reader-passwd = mkOption { type = str; description = '' Password for the user specified in ldap-reader-dn. ''; }; }; }; dovecot-user = config.services.dovecot2.user; in { options.fudo.mail-server.dovecot = with types; { ssl-private-key = mkOption { type = str; description = "Location of the server SSL private key."; }; ssl-certificate = mkOption { type = str; description = "Location of the server SSL certificate."; }; ldap = mkOption { type = nullOr (submodule ldapOpts); default = null; description = '' LDAP auth server configuration. If omitted, the server will use local authentication. ''; }; }; config = mkIf cfg.enable { services.prometheus.exporters.dovecot = mkIf cfg.monitoring { enable = true; scopes = ["user" "global"]; listenAddress = "127.0.0.1"; port = 9166; socketPath = "/var/run/dovecot2/old-stats"; }; services.dovecot2 = { enable = true; enableImap = true; enableLmtp = true; enablePop3 = true; enablePAM = cfg.dovecot.ldap == null; createMailUser = true; mailUser = cfg.mail-user; mailGroup = cfg.mail-group; mailLocation = "maildir:${cfg.mail-directory}/%u/"; sslServerCert = cfg.dovecot.ssl-certificate; sslServerKey = cfg.dovecot.ssl-private-key; modules = [ pkgs.dovecot_pigeonhole ]; protocols = [ "sieve" ]; sieveScripts = { after = builtins.toFile "spam.sieve" '' require "fileinto"; if header :is "X-Spam" "Yes" { fileinto "Junk"; stop; } ''; }; mailboxes = cfg.mailboxes; extraConfig = '' #Extra Config ${optionalString cfg.monitoring '' # The prometheus exporter still expects an older style of metrics mail_plugins = $mail_plugins old_stats service old-stats { unix_listener old-stats { user = dovecot-exporter group = dovecot-exporter } } ''} ${lib.optionalString cfg.debug '' mail_debug = yes auth_debug = yes verbose_ssl = yes ''} protocol imap { mail_max_userip_connections = ${toString cfg.max-user-connections} mail_plugins = $mail_plugins imap_sieve } protocol pop3 { mail_max_userip_connections = ${toString cfg.max-user-connections} } protocol lmtp { mail_plugins = $mail_plugins sieve } mail_access_groups = ${cfg.mail-group} ssl = required # When looking up usernames, just use the name, not the full address auth_username_format = %n service lmtp { # Enable logging in debug mode ${optionalString cfg.debug "executable = lmtp -L"} # Unix socket for postfix to deliver messages via lmtp unix_listener dovecot-lmtp { user = "postfix" group = ${cfg.mail-group} mode = 0600 } # Drop privs, since all mail is owned by one user # user = ${cfg.mail-user} # group = ${cfg.mail-group} user = root } auth_mechanisms = login plain ${optionalString (cfg.dovecot.ldap != null) (ldap-passwd-entry cfg.dovecot.ldap)} userdb { driver = static args = uid=${toString cfg.mail-user-id} home=${cfg.mail-directory}/%u } # Used by postfix to authorize users service auth { unix_listener auth { mode = 0660 user = "${config.services.postfix.user}" group = ${cfg.mail-group} } unix_listener auth-userdb { mode = 0660 user = "${config.services.postfix.user}" group = ${cfg.mail-group} } } service auth-worker { user = root } service imap { vsz_limit = 1024M } namespace inbox { separator = "/" inbox = yes } plugin { sieve_plugins = sieve_imapsieve sieve_extprograms sieve = file:/var/sieve/%u/scripts;active=/var/sieve/%u/active.sieve sieve_default = file:/var/sieve/%u/default.sieve sieve_default_name = default # From elsewhere to Spam folder imapsieve_mailbox1_name = Junk imapsieve_mailbox1_causes = COPY imapsieve_mailbox1_before = file:${state-directory}/imap_sieve/report-spam.sieve # From Spam folder to elsewhere imapsieve_mailbox2_name = * imapsieve_mailbox2_from = Junk imapsieve_mailbox2_causes = COPY imapsieve_mailbox2_before = file:${state-directory}/imap_sieve/report-ham.sieve sieve_pipe_bin_dir = ${pipe-bin}/pipe/bin sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment } recipient_delimiter = + lmtp_save_to_detail_mailbox = yes lda_mailbox_autosubscribe = yes lda_mailbox_autocreate = yes ''; }; systemd.services.dovecot2.preStart = '' mkdir -p '${state-directory}' chown ${dovecot-user}:${cfg.mail-group} '${state-directory}' rm -rf '${state-directory}/imap_sieve' mkdir '${state-directory}/imap_sieve' cp -p "${./dovecot/imap_sieve}"/*.sieve '${state-directory}/imap_sieve/' for k in "${state-directory}/imap_sieve"/*.sieve ; do ${pkgs.dovecot_pigeonhole}/bin/sievec "$k" done chown -R '${dovecot-user}:${cfg.mail-group}' '${state-directory}/imap_sieve' chown '${cfg.mail-user}:${cfg.mail-group}' ${cfg.mail-directory} chmod g+w ${cfg.mail-directory} ''; }; }