nixos-config/lib/fudo/mail/dovecot.nix
2021-11-05 07:06:08 -07:00

315 lines
8.5 KiB
Nix

{ config, lib, pkgs, environment, ... }:
with lib;
let
cfg = config.fudo.mail-server;
sieve-path = "${cfg.state-directory}/dovecot/imap_sieve";
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-template = ldap-cfg:
let
ssl-config = if (ldap-cfg.ca == null) then ''
tls = no
tls_require_cert = try
'' else ''
tls_ca_cert_file = ${ldap-cfg.ca}
tls = yes
tls_require_cert = try
'';
in
pkgs.writeText "dovecot2-ldap-config.conf.template" ''
uris = ${concatStringsSep " " ldap-cfg.server-urls}
ldap_version = 3
dn = ${ldap-cfg.reader-dn}
dnpass = __LDAP_READER_PASSWORD__
auth_bind = yes
auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org
base = dc=fudo,dc=org
${ssl-config}
'';
ldap-conf-generator = ldap-cfg: let
template = ldap-conf-template ldap-cfg;
target-dir = dirOf ldap-cfg.generated-ldap-config;
target = ldap-cfg.generated-ldap-config;
in pkgs.writeScript "dovecot2-ldap-password-swapper.sh" ''
mkdir -p ${target-dir}
touch ${target}
chmod 600 ${target}
chown ${config.services.dovecot2.user} ${target}
LDAP_READER_PASSWORD=$( cat "${ldap-cfg.reader-password-file}" )
sed 's/__LDAP_READER_PASSWORD__/$LDAP_READER_PASSWORD/' '${template}' > ${target}
'';
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;
};
base = mkOption {
type = str;
description = "Base of the LDAP server database.";
example = "dc=fudo,dc=org";
};
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-password-file = mkOption {
type = str;
description = "Password for the user specified in ldap-reader-dn.";
};
generated-ldap-config = mkOption {
type = str;
description = "Path at which to store the generated LDAP config file, including password.";
default = "/run/dovecot2/config/ldap.conf";
};
};
};
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) ''
passdb {
driver = ldap
args = ${cfg.dovecot.ldap.generated-ldap-config}
}
''}
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:${sieve-path}/report-spam.sieve
# From Spam folder to elsewhere
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:${sieve-path}/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 = {
tmpfiles.rules = [
"d ${sieve-path} 750 ${dovecot-user} ${cfg.mail-group} - -"
];
services.dovecot2.preStart = ''
rm -f ${sieve-path}/*
cp -p ${./dovecot/imap_sieve}/*.sieve ${sieve-path}
for k in ${sieve-path}/*.sieve ; do
${pkgs.dovecot_pigeonhole}/bin/sievec "$k"
done
${optionalString (cfg.dovecot.ldap != null)
(ldap-conf-generator cfg.dovecot.ldap)}
'';
};
};
}