Initial checkin
This commit is contained in:
commit
49897ba6fe
|
@ -0,0 +1,58 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let cfg = config.fudo.mail.clamav;
|
||||
|
||||
in {
|
||||
options.fudo.mail.clamav = with types; {
|
||||
enable = mkEnableOption "Enable virus scanning with ClamAV.";
|
||||
|
||||
state-directory = mkOption {
|
||||
type = str;
|
||||
description = "Path at which to store ClamAV database.";
|
||||
default = "/var/lib/clamav";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to listen for incoming requests.";
|
||||
default = 15407;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users = {
|
||||
users.clamav = {
|
||||
isSystemUser = true;
|
||||
uid = config.ids.uids.clamav;
|
||||
home = mkForce cfg.state-directory;
|
||||
description = "ClamAV daemon user";
|
||||
group = "clamav";
|
||||
};
|
||||
groups.clamav = {
|
||||
members = [ "clamav" ];
|
||||
gid = config.ids.gids.clamav;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules =
|
||||
[ "d ${cfg.state-directory} 0750 clamav clamav - -" ];
|
||||
|
||||
services.clamav = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PhishingScanURLs = "no";
|
||||
DatabaseDirectory = mkForce cfg.state-directory;
|
||||
User = "clavmav";
|
||||
TCPSocket = cfg.port;
|
||||
};
|
||||
};
|
||||
updater = {
|
||||
enable = true;
|
||||
settings = {
|
||||
DatabaseDirectory = mkForce cfg.state-directory;
|
||||
DatabaseOwner = "clamav";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.mail.dkim;
|
||||
|
||||
ensureDomainDkimCert = keyDir: domain:
|
||||
let
|
||||
dkimKey = "${keyDir}/${domain}.mail.key";
|
||||
dkimTxt = "${keyDir}/${domain}.mail.txt";
|
||||
in ''
|
||||
if [ ! -f "${dkimKey}" ] || [ ! -f ${dkimTxt} ]; then
|
||||
opendkim-genkey \
|
||||
-s mail \
|
||||
-d ${domain} \
|
||||
--bits="${toString cfg.key-bits}" \
|
||||
--directory=$TMPDIR
|
||||
mv $TMPDIR/mail.private ${dkimKey}
|
||||
mv $TMPDIR/mail.txt ${dkimTxt}
|
||||
fi
|
||||
'';
|
||||
|
||||
ensureAllDkimCerts = keyDir:
|
||||
domains concatStringsSep "\n" (map (ensureDomainDkimCert keyDir) domains);
|
||||
|
||||
makeKeyTable = keyDir: domains:
|
||||
pkgs.writeText "opendkim-key-table" (concatStringsSep "\n"
|
||||
(map (dom: "${dom}:mail:${keyDir}/${dom}.mail.key")));
|
||||
|
||||
makeSigningTable = domains:
|
||||
pkgs.writeText "opendkim-signing-table"
|
||||
(concatStringsSep "\n" (map (dom: "${dom} ${dom}") domains));
|
||||
|
||||
in {
|
||||
options.fudo.mail.dkim = with types; {
|
||||
enable = mkEnableOption "Enable DKIM signature verification.";
|
||||
|
||||
debug = mkEnableOption "Enable debug logs.";
|
||||
|
||||
domains = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"List of domains to be considered local, and signed instead of verified.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "Port at which to listen for incoming signing requests.";
|
||||
default = 5324;
|
||||
};
|
||||
|
||||
state-directory = mkOption {
|
||||
type = str;
|
||||
description = "Directory at which to store DKIM state (i.e. keys).";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.opendkim = {
|
||||
enable = true;
|
||||
selector = cfg.selector;
|
||||
domains = let domainString = concatStringsSep "," cfg.domains;
|
||||
in "csl:${domainsString}";
|
||||
configFile = let
|
||||
debugString = ''
|
||||
Syslog yes
|
||||
SyslogSuccess yes
|
||||
LogWhy yes
|
||||
'';
|
||||
in pkgs.writeText "opendkim.conf" ''
|
||||
Canonicalization relaxed/simple
|
||||
Socket inet:${toString cfg.port}
|
||||
KeyTable file: ${keyTable}
|
||||
SigningTable file:${signingTable}
|
||||
${optionalString cfg.debug debugString}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd = {
|
||||
tmpfiles.rules = let
|
||||
user = config.services.opendkim.user;
|
||||
group = config.services.opendkim.group;
|
||||
in [ "d ${cfg.state-directory} 0700 ${user} ${group} - -" ];
|
||||
services.opendkim = {
|
||||
serviceConfig.ReadWritePaths = [ cfg.state-directory ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,440 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
sievePath = let
|
||||
isRegularFile = _: type: type == "regular";
|
||||
sieves = filter isRegularFile (builtins.readDir ./sieves);
|
||||
headOrNull = lst: if lst == [ ] then null else head lst;
|
||||
stripExt = ext: filename: headOrNull (match "(.+)[.]${ext}$" filename);
|
||||
compileFile = filename:
|
||||
let
|
||||
filePath = ./sieves + "/${filename}";
|
||||
fileBaseName = stripExt "sieve" filename;
|
||||
in "sievec ${filePath} $out/${fileBaseName}.svbin";
|
||||
in pkgs.stdenv.mkDerivation {
|
||||
name = "dovecot-sieves";
|
||||
buildInputs = with pkgs; [ dovecot_pidgeonhole ];
|
||||
phases = [ "installPhase" ];
|
||||
buildPhase = ''
|
||||
mkdir -p $out
|
||||
${concatStringsSep "\n" (map compileFile sieves)}
|
||||
'';
|
||||
};
|
||||
|
||||
in {
|
||||
options.fudo.mail.dovecot = with types; {
|
||||
enable = mkEnableOption "Enable Dovecot2 IMAP server.";
|
||||
|
||||
debug = mkEnableOption "Enable debug logs.";
|
||||
|
||||
state-directory = mkOption {
|
||||
type = str;
|
||||
description = "Directory at which to store server state.";
|
||||
};
|
||||
|
||||
ports = {
|
||||
lmtp = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to listen for LMTP connections.";
|
||||
default = 24;
|
||||
};
|
||||
auth = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to listen for auth requests.";
|
||||
default = 5447;
|
||||
};
|
||||
userdb = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to listen for userdb requests.";
|
||||
default = 5448;
|
||||
};
|
||||
metrics = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to serve metrics data.";
|
||||
default = 5034;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
mail-user = mkOption {
|
||||
type = str;
|
||||
description = "User as which to run store & access mail.";
|
||||
default = "fudo-mail";
|
||||
};
|
||||
|
||||
mail-group = mkOption {
|
||||
type = str;
|
||||
description = "Group as which to store & access mail.";
|
||||
default = "fudo-mail";
|
||||
};
|
||||
|
||||
ssl = {
|
||||
certificate = mkOption {
|
||||
type = str;
|
||||
description = "Location of the Dovecot SSL certificate.";
|
||||
};
|
||||
|
||||
private-key = mkOption {
|
||||
type = str;
|
||||
description = "Location of the Dovecot SSL private key.";
|
||||
};
|
||||
};
|
||||
|
||||
metrics = {
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User as which to fetch metrics.";
|
||||
default = "dovecot-metrics";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = str;
|
||||
description = "Group as which to fetch metrics.";
|
||||
default = "dovecot-metrics";
|
||||
};
|
||||
};
|
||||
|
||||
mailboxes = let
|
||||
mailboxOpts = { name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = str;
|
||||
description = "Mailbox Name.";
|
||||
default = name;
|
||||
};
|
||||
auto = mkOption {
|
||||
type = enum [ "no" "create" "subscribe" ];
|
||||
description = "Whether to auto-create/subscribe.";
|
||||
default = "no";
|
||||
};
|
||||
specialUse = mkOption {
|
||||
type = nullOr (enum [
|
||||
"All"
|
||||
"Archive"
|
||||
"Drafts"
|
||||
"Flagged"
|
||||
"Junk"
|
||||
"Sent"
|
||||
"Trash"
|
||||
]);
|
||||
description = "Mailbox special use.";
|
||||
default = null;
|
||||
};
|
||||
autoexpunge = mkOption {
|
||||
type = nullOr str;
|
||||
description =
|
||||
"How long to wait before clearing mail from this mailbox. Null is never.";
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
in mkOption {
|
||||
type = attrsOf (submodule mailboxOpts);
|
||||
description = "Mailboxes to be created for dovecot.";
|
||||
default = {
|
||||
Trash = {
|
||||
auto = "create";
|
||||
specialUse = "Trash";
|
||||
autoexpunge = "30d";
|
||||
};
|
||||
Junk = {
|
||||
auto = "create";
|
||||
specialUse = "Junk";
|
||||
autoexpunge = "60d";
|
||||
};
|
||||
Drafts = {
|
||||
auto = "create";
|
||||
specialUse = "Drafts";
|
||||
autoexpunge = "60d";
|
||||
};
|
||||
Sent = {
|
||||
auto = "create";
|
||||
specialUse = "Sent";
|
||||
};
|
||||
Archive = {
|
||||
atuo = "no";
|
||||
specialUse = "Archive";
|
||||
};
|
||||
Flagged = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Flagged";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
rspamd = {
|
||||
host = mkOption {
|
||||
type = str;
|
||||
description = "Host to which spam/ham will be forwarded.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = str;
|
||||
description = "Port to which spam/ham will be forwarded.";
|
||||
};
|
||||
};
|
||||
|
||||
max-user-connections = mkOption {
|
||||
type = int;
|
||||
description = "Maximum allowed simultaneous connections by one user.";
|
||||
default = 5;
|
||||
};
|
||||
|
||||
ldap = let
|
||||
ldapOpts = {
|
||||
options = {
|
||||
host = mkOption {
|
||||
type = str;
|
||||
description = "LDAP hostname.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = str;
|
||||
description = "Port on which LDAP is listening.";
|
||||
};
|
||||
|
||||
base = mkOption {
|
||||
type = str;
|
||||
description = "Base of the LDAP server database.";
|
||||
example = "dc=mydomain,dc=org";
|
||||
};
|
||||
|
||||
bind-dn = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
DN used for fetching user information.
|
||||
|
||||
Needs access to homeDirectory, uidNumber, gidNumber, and uid, but not
|
||||
password attributes.
|
||||
'';
|
||||
};
|
||||
|
||||
bind-password-file = mkOption {
|
||||
type = str;
|
||||
description = "Path to file containing bind password for bind-dn.";
|
||||
};
|
||||
};
|
||||
};
|
||||
in mkOption {
|
||||
type = nullOr ldapOpts;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services = {
|
||||
prometheus.exporters.dovecot = {
|
||||
enable = true;
|
||||
scopes = [ "user" "global" ];
|
||||
user = cfg.metrics.user;
|
||||
listenAddresses = "127.0.0.1";
|
||||
port = cfg.metrics.port;
|
||||
socketPath = "/var/run/dovecot2/old-stats";
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
users = {
|
||||
"${cfg.mail-user}" = {
|
||||
isSystemUser = true;
|
||||
group = cfg.mail-group;
|
||||
};
|
||||
"${cfg.metrics.user}" = {
|
||||
isSystemUser = true;
|
||||
group = cfg.metrics.group;
|
||||
};
|
||||
};
|
||||
groups = {
|
||||
"${cfg.mail-group}".members = [ cfg.mail-user ];
|
||||
"${cfg.metrics.group}".members = [ cfg.metrics.user ];
|
||||
};
|
||||
};
|
||||
|
||||
systemd = {
|
||||
tmpfiles.rules = [
|
||||
"d ${cfg.state-directory} 0750 ${cfg.mail-user} ${cfg.mail-group} - -"
|
||||
"d ${cfg.state-directory}/mail 0750 ${cfg.mail-user} ${cfg.mail-group} - -"
|
||||
"d ${cfg.state-directory}/sieves 0750 ${cfg.mail-user} ${cfg.mail-group} - -"
|
||||
];
|
||||
};
|
||||
|
||||
dovecot2 = {
|
||||
enable = true;
|
||||
enableImap = true;
|
||||
enableLmtp = true;
|
||||
enablePAM = cfg.ldap == null;
|
||||
|
||||
mailUser = cfg.mail-user;
|
||||
mailGroup = cfg.mail-group;
|
||||
mailLocation = "maildir:${cfg.state-directory}/mail//%u/";
|
||||
createMailUser = false;
|
||||
|
||||
sslServerCert = cfg.ssl.certificate;
|
||||
sslServerKey = cfg.ssl.private-key;
|
||||
|
||||
mailboxes = cfg.mailboxes;
|
||||
|
||||
modules = with pkgs; [ dovecot_pidgeonhole ];
|
||||
protocols = [ "sieve" ];
|
||||
|
||||
mailPlugins.globally.enable = [ "old_stats" ];
|
||||
|
||||
sieveScripts = {
|
||||
after = builtins.toFile "spam.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "Junk";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = let
|
||||
# Add learn_ham & learn_spam to dovecot2 path for use by sieves
|
||||
pipeBin = let
|
||||
teachRspamd = msg:
|
||||
pkgs.writeShellApplication {
|
||||
name = "rspamd_${msg}";
|
||||
runtimeInputs = with pkgs; [ rspamd ];
|
||||
text =
|
||||
"exec rspamc -h ${cfg.rspamd.host}:${cfg.rspam.port} ${msg}";
|
||||
};
|
||||
learnHam = teachRspamd "learn_ham";
|
||||
learnSpam = teachRspamd "learn_spam";
|
||||
in pkgs.buildEnv {
|
||||
name = "rspam_pipe_bin";
|
||||
paths = [ learnHam learnSpam ];
|
||||
};
|
||||
in ''
|
||||
## Extra Config
|
||||
|
||||
mail_plugins = $mail_plugins
|
||||
|
||||
${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 lmtp {
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
|
||||
mail_access_groups = ${cfg.mail-group}
|
||||
|
||||
# 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"}
|
||||
|
||||
inet_listener dovecot-lmtp {
|
||||
address = 0.0.0.0
|
||||
port = ${toString cfg.port.lmtp}
|
||||
}
|
||||
|
||||
# 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.state-directory}/mail/%u
|
||||
}
|
||||
|
||||
# Used by postfix to authorize users
|
||||
service auth {
|
||||
inet_listener auth {
|
||||
address = 0.0.0.0
|
||||
port = ${toString cfg.ports.auth}
|
||||
}
|
||||
inet_listener auth-userdb {
|
||||
address = 0.0.0.0
|
||||
port = ${toString cfg.ports.userdb}
|
||||
}
|
||||
}
|
||||
|
||||
service auth-worker {
|
||||
user = ${config.services.dovecot2.user}
|
||||
idle_kill = 3
|
||||
}
|
||||
|
||||
service imap {
|
||||
vsz_limit = 1024M
|
||||
}
|
||||
|
||||
namespace inbox {
|
||||
separator = "/"
|
||||
inbox = yes
|
||||
}
|
||||
|
||||
plugin {
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
sieve = file:${cfg.state-directory}/sieves/%u/scripts;active=${cfg.state-directory}/sieves/%u/active.sieve
|
||||
sieve_default = file:${cfg.sieve-directory}/%u/default.sieve
|
||||
sieve_default_name = default
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Junk
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:${sievePath}/report-spam.sieve
|
||||
# From Spam folder to elsewhere
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Junk
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:${sievePath}/report-ham.sieve
|
||||
|
||||
sieve_pipe_bin_dir = ${pipeBin}/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
|
||||
|
||||
service old-stats {
|
||||
unix_listener old-stats {
|
||||
user = ${cfg.metrics.user}
|
||||
group = ${cfg.metrics.group}
|
||||
}
|
||||
fifo_listener old-stats-mail {
|
||||
mode = 0660
|
||||
user = ${config.services.dovecot2.user}
|
||||
group = ${config.services.dovecot2.group}
|
||||
}
|
||||
fifo_listener old-stats-user {
|
||||
mode = 0660
|
||||
user = ${config.services.dovecot2.user}
|
||||
group = ${config.services.dovecot2.group}
|
||||
}
|
||||
}
|
||||
|
||||
plugin {
|
||||
old_stats_refresh = 30 secs
|
||||
old_stats_track_cmds = yes
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
description = "Mail server running in containers.";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-23.05";
|
||||
arion.url = "github:hercules-ci/arion";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, arion, ... }: {
|
||||
nixosModules = rec {
|
||||
default = mailServerContainer;
|
||||
mailServerContainer = { ... }: {
|
||||
imports = [ arion.nixosModules.arion ./mail-server.nix ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib; {
|
||||
options.fudo.mail = with types; {
|
||||
enable = mkEnableOption "Enable mail server.";
|
||||
|
||||
state-directory = mkOption {
|
||||
type = str;
|
||||
description = "Directory at which to store server state.";
|
||||
};
|
||||
|
||||
primary-domain = mkOption {
|
||||
type = str;
|
||||
description = "Primary domain name served by this server.";
|
||||
};
|
||||
|
||||
extra-domains = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of additional domains served by this server.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
smtp = {
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Hostname too use for the SMTP server. Must resolve to this host.";
|
||||
default = "smtp.${config.fudo.mail.primary-domain}";
|
||||
};
|
||||
|
||||
ssl = {
|
||||
certificate = mkOption {
|
||||
type = str;
|
||||
description = "SSL certificate for the SMTP host.";
|
||||
};
|
||||
private-key = mkOption {
|
||||
type = str;
|
||||
description = "SSL private key for the SMTP host.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
imap = {
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Hostname too use for the IMAP server. Must resolve to this host.";
|
||||
default = "imap.${config.fudo.mail.primary-domain}";
|
||||
};
|
||||
|
||||
ssl = {
|
||||
certificate = mkOption {
|
||||
type = str;
|
||||
description = "SSL certificate for the IMAP host.";
|
||||
};
|
||||
private-key = mkOption {
|
||||
type = str;
|
||||
description = "SSL private key for the IMAP host.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
services.nginx = {
|
||||
virtualHosts = {
|
||||
"${cfg.smtp.hostname}".locations."/metrics" = {
|
||||
proxyPass = "http://localhost:${metricsPort}/metrics";
|
||||
};
|
||||
"${cfg.imap.hostname}".locations."/metrics" = {
|
||||
proxyPass = "http://localhost:${metricsPort}/metrics";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.arion.projects.mail-server.settings = let
|
||||
image = { pkgs, ... }: {
|
||||
project.name = "fudo-mailserver";
|
||||
networks = {
|
||||
external_network.internal = false;
|
||||
internal_network.internal = true;
|
||||
};
|
||||
serices = let
|
||||
antivirusPort = 15407;
|
||||
antispamPort = 11335;
|
||||
lmtpPort = 24;
|
||||
authPort = 5447;
|
||||
userdbPort = 5448;
|
||||
metricsPort = 5034;
|
||||
in {
|
||||
smtp = {
|
||||
networks = [
|
||||
"internal_network"
|
||||
# Needs access to internet to forward emails
|
||||
"external_network"
|
||||
];
|
||||
ports = [ "25:25" "587:587" "465:465" "2525:2525" ];
|
||||
nixos = {
|
||||
useSystemd = true;
|
||||
configuration = [
|
||||
(import ./postfix.nix)
|
||||
{
|
||||
boot.tmpOnTmpfs = true;
|
||||
system.nssModules = lib.mkForce [ ];
|
||||
fudo.mail.postfix = {
|
||||
enable = true;
|
||||
debug = cfg.debug;
|
||||
domain = cfg.primary-domain;
|
||||
local-domains = cfg.extra-domains;
|
||||
hostname = cfg.smtp.hostname;
|
||||
trusted-networks = cfg.trusted-networks;
|
||||
blacklist = {
|
||||
senders = cfg.blacklist.senders;
|
||||
recipients = cfg.blacklist.recipients;
|
||||
dns = cfg.blacklist.dns;
|
||||
};
|
||||
aliases = {
|
||||
user-aliases = cfg.user-aliases;
|
||||
alias-users = cfg.alias-users;
|
||||
};
|
||||
ssl = {
|
||||
certificate = cfg.smtp.ssl.certificate;
|
||||
private-key = cfg.smtp.ssl.private-key;
|
||||
};
|
||||
sasl-domain = cfg.sasl-domain;
|
||||
message-size-limit = cfg.message-size-limit;
|
||||
ports = { metrics = metricsPort; };
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
imap = {
|
||||
networks = [ "internal_network" ];
|
||||
ports = [ "143:143" "993:993" ];
|
||||
nixos = {
|
||||
useSystemd = true;
|
||||
configuration = [
|
||||
(import ./dovecot.nix)
|
||||
{
|
||||
boot.tmpOnTmpfs = true;
|
||||
system.nssModules = lib.mkForce [ ];
|
||||
fudo.mail.dovecot = {
|
||||
enable = true;
|
||||
debug = cfg.debug;
|
||||
state-directory = "${cfg.state-directory}/dovecot";
|
||||
ports = {
|
||||
lmtp = lmtpPort;
|
||||
auth = authPort;
|
||||
userdb = userdbPort;
|
||||
metrics = metricsPort;
|
||||
};
|
||||
mail-user = cfg.mail-user;
|
||||
mail-group = cfg.mail-group;
|
||||
ssl = {
|
||||
certificate = cfg.imap.ssl.certificate;
|
||||
private-key = cfg.imap.ssl.private-key;
|
||||
};
|
||||
rspamd = {
|
||||
host = "antispam";
|
||||
port = antispamPort;
|
||||
};
|
||||
ldap = mkIf cfg.ldap-proxy {
|
||||
host = "ldap-proxy";
|
||||
port = 3389;
|
||||
base = cfg.ldap.base;
|
||||
bind-dn = cfg.ldap.bind-dn;
|
||||
bind-password-file = cfg.ldap.bind-password-file;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
ldap-proxy.service = mkIf (cfg.ldap-proxy != null) {
|
||||
image = cfg.images.ldap-proxy;
|
||||
restart = "always";
|
||||
networks = [
|
||||
"internal_network"
|
||||
# Needs access to external network for user lookups
|
||||
"external_network"
|
||||
];
|
||||
envFile = hostSecrets.mailLdapProxyEnv.target-file;
|
||||
};
|
||||
antispam = {
|
||||
networks = [
|
||||
"internal_network"
|
||||
# Needs external access for blacklist checks
|
||||
"external_network"
|
||||
];
|
||||
nixos = {
|
||||
useSystemd = true;
|
||||
configuration = [
|
||||
(import ./rspamd.nix)
|
||||
{
|
||||
boot.tmpOnTmpfs = true;
|
||||
system.nssModules = lib.mkForce [ ];
|
||||
fudo.mail.rspamd = {
|
||||
enable = true;
|
||||
ports = {
|
||||
milter = antispamPort;
|
||||
controller = antispamControllerPort;
|
||||
metrics = metricsPort;
|
||||
};
|
||||
antivirus = {
|
||||
host = "antivirus";
|
||||
port = antivirusPort;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
antivirus = {
|
||||
networks = [
|
||||
"internal_network"
|
||||
# Needs external access for database updates
|
||||
"external_network"
|
||||
];
|
||||
nixos = {
|
||||
useSystemd = true;
|
||||
configuration = [
|
||||
(import ./clamav.nix)
|
||||
{
|
||||
boot.tmpOnTmpfs = true;
|
||||
system.nssModules = lib.mkForce [ ];
|
||||
fudo.mail.clamav = {
|
||||
enable = true;
|
||||
state-directory = "${cfg.state-directory}/rspamd";
|
||||
port = antispamPort;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
dkim = {
|
||||
networks = [ "internal_network" ];
|
||||
nixos = {
|
||||
useSystemd = true;
|
||||
configuration = [
|
||||
(import ./dkim.nix)
|
||||
{
|
||||
boot.tmpOnTmpfs = true;
|
||||
system.nssModules = lib.mkForce [ ];
|
||||
fudo.mail.dkim = {
|
||||
enable = true;
|
||||
debug = cfg.debug;
|
||||
domains = [ cfg.primary-domain ] ++ cfg.extra-domains;
|
||||
};
|
||||
port = dkimPort;
|
||||
state-directory = "${cfg.state-directory}/dkim";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
metrics-proxy = {
|
||||
networks = [ "internal_network" ];
|
||||
ports = [ "${cfg.metricsPort}:80" ];
|
||||
nixos = {
|
||||
useSystemd = true;
|
||||
configuration = {
|
||||
boot.tmpOnTmpfs = true;
|
||||
system.nssModules = lib.mkForce [ ];
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
virtualHosts.localhost = {
|
||||
default = true;
|
||||
locations = {
|
||||
"/postfix" = {
|
||||
proxyPass = "http://smtp:${metricsPort}/";
|
||||
};
|
||||
"/dovecot" = {
|
||||
proxyPass = "http://imap:${metricsPort}/";
|
||||
};
|
||||
"rspamd" = {
|
||||
proxyPass = "http://antispam:${metricsPort}/";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in { imports = [ image ]; };
|
||||
};
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let allDomains = [ cfg.domain ] ++ cfg.local-domains;
|
||||
|
||||
in {
|
||||
options.fudo.mail.postfix = with types; {
|
||||
enable = mkEnableOption "Enable Postfix SMTP server.";
|
||||
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = "Primary domain served by this mail server.";
|
||||
};
|
||||
|
||||
local-domains = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"List of domains to be considered local to this server. Don't include the primary domain.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description = "Fully-qualified hostname of this mail server.";
|
||||
};
|
||||
|
||||
trusted-networks = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of trusted network ranges.";
|
||||
};
|
||||
|
||||
blacklist = {
|
||||
senders = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"List of email addresses for which we will never send email.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
recipients = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"List of email addresses for which we will not accept email.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
dns = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of DNS spam blacklists to use.";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
aliases = {
|
||||
user-aliases = mkOption {
|
||||
type = attrsOf (listOf str);
|
||||
description =
|
||||
"Map of username to list of emails belonging to that user.";
|
||||
default = { };
|
||||
example = { some_user = [ "foo@bar.com" "baz@bar.com" ]; };
|
||||
};
|
||||
|
||||
alias-users = mkOption {
|
||||
type = attrsOf (listOf str);
|
||||
description =
|
||||
"Map of aliases to list of accounts which should receive incoming email.";
|
||||
default = { };
|
||||
example = { hostmaster = [ "admin0" "admin1" ]; };
|
||||
};
|
||||
};
|
||||
|
||||
ssl = {
|
||||
certificate = mkOption {
|
||||
type = str;
|
||||
description = "Location of host SSL certificate.";
|
||||
};
|
||||
|
||||
private-key = mkOption {
|
||||
type = str;
|
||||
description = "Location of host SSL private key.";
|
||||
};
|
||||
};
|
||||
|
||||
sasl-domain = mkOption {
|
||||
type = str;
|
||||
description = "SASL domain to use for authentication.";
|
||||
};
|
||||
|
||||
policy-spf.extraConfig = mkOption {
|
||||
type = str;
|
||||
default = "";
|
||||
example = "skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1";
|
||||
description = "Extra configuration options for policyd-spf.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User as which to run Postfix server.";
|
||||
default = "postfix";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = str;
|
||||
description = "Group as which to run Postfix server.";
|
||||
default = "postfix";
|
||||
};
|
||||
|
||||
message-size-limit = mkOption {
|
||||
type = int;
|
||||
description = "Max size of email messages, in MB.";
|
||||
default = 200;
|
||||
};
|
||||
|
||||
ports = {
|
||||
metrics = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to listen for metrics requests.";
|
||||
default = 1725;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users = {
|
||||
users."${cfg.user}" = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
};
|
||||
|
||||
groups."${cfg.group}".members = [ cfg.user ];
|
||||
};
|
||||
|
||||
services = {
|
||||
prometheus.exporters.postfix = {
|
||||
enable = true;
|
||||
systemd.enable = true;
|
||||
showqPath = "/var/lib/postfix/queue/public/showq";
|
||||
group = config.services.postfix.group;
|
||||
listenAddress = "127.0.0.1";
|
||||
port = cfg.metrics-port;
|
||||
};
|
||||
|
||||
postfix = {
|
||||
enable = true;
|
||||
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
|
||||
domain = cfg.domain;
|
||||
origin = cfg.domain;
|
||||
hostname = cfg.hostname;
|
||||
destination = [ "localhost" "localhost.localdomain" ];
|
||||
|
||||
enableHeaderChecks = true;
|
||||
enableSmtp = true;
|
||||
enableSubmission = true;
|
||||
useSrs = true;
|
||||
|
||||
dnsBlacklists = cfg.dns-blacklists;
|
||||
|
||||
concatMapAttrsToList = f: as: concatLists (mapAttrsToList f as);
|
||||
|
||||
mapFiles = let
|
||||
writeEntries = filename: entries:
|
||||
pkgs.writeText filename (concatStringsSep "\n" entries);
|
||||
mkRejectList = entries: map (entry: "${entry} REJECT") entries;
|
||||
escapeDot = replaceString [ "." ] [ "\\." ];
|
||||
in {
|
||||
reject_senders = writeEntries "sender_blacklist"
|
||||
(mkRejectList cfg.blacklist.senders);
|
||||
reject_recipients = writeEntries "recipient_blacklist"
|
||||
(mkRejectList cfg.blacklist.recipients);
|
||||
virtual_mailbox_map = writeEntries "virtual_mailbox_map"
|
||||
(map (domain: "@${domain} OK") allDomains);
|
||||
sender_login_map = writeEntries "sender_login_maps"
|
||||
(map (domain: "/^(.*)@${escapeDot domain}$/ \${1}") allDomains);
|
||||
};
|
||||
|
||||
networks = cfg.trusted-networks;
|
||||
|
||||
virtual = let
|
||||
mkEmail = domain: user: "${user}@${domain}";
|
||||
mkUserAliases = concatMapAttrsToList (user: aliases:
|
||||
map (alias: "${alias} ${mkEmail cfg.domain user}"));
|
||||
mkAliasUsers = domains:
|
||||
let
|
||||
userList = users:
|
||||
concatStringsSep "," (map (mkEmail cfg.domain) users);
|
||||
in concatMapAttrsToList (alias: users:
|
||||
concatMap
|
||||
(domain: "${mkEmail domain alias} ${mkAliasUsers users}"));
|
||||
in concatStringsSep "\n" ((mkUserAliases cfg.aliases.user-aliases)
|
||||
++ (mkAliasUsers allDomains cfg.aliases.alias-users));
|
||||
|
||||
sslCert = cfg.ssl.certificate;
|
||||
sslKey = cfg.ssl.private-key;
|
||||
|
||||
config = let
|
||||
pcreFile = name: "pcre:/var/lib/postfix/conf/${name}";
|
||||
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
||||
|
||||
in {
|
||||
virtual_mailbox_domains = allDomains;
|
||||
virtual_mailbox_maps = mappedFile "virtual_mailbox_map";
|
||||
|
||||
## I don't think these are needed...
|
||||
# virtual_uid_maps = let uid = config.users.users."${cfg.user}".uid;
|
||||
# in "static:${toString uid}";
|
||||
# virtual_gid_maps = let gid = config.users.groups."${cfg.group}".gid;
|
||||
# in "static: ${toString gid}";
|
||||
|
||||
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
||||
|
||||
message_size_limit = toString (cfg.message-size-limit * 1024 * 1024);
|
||||
|
||||
stmpd_banner = "${cfg.hostname} ESMTP NO UCE";
|
||||
|
||||
tls_eecdh_strong_curve = "prime256v1";
|
||||
tls_eecdh_ultra_curve = "secp384r1";
|
||||
|
||||
policy-spf_time_limit = "3600s";
|
||||
|
||||
smtp_host_lookup = "dns, native";
|
||||
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||
smtpd_sasl_auth_enable = "yes";
|
||||
smtpd_sasl_local_domain = cfg.sasl-domain;
|
||||
|
||||
smtpd_sasl_security_options = "noanonymous";
|
||||
smtpd_sasl_tls_security_options = "noanonymous";
|
||||
|
||||
smtpd_sender_login_maps = (pcreFile "sender_login_map");
|
||||
|
||||
disable_vrfy_command = "yes";
|
||||
|
||||
recipient_delimiter = "+";
|
||||
|
||||
milter_protocol = "6";
|
||||
milter_mail_macros =
|
||||
"i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
|
||||
|
||||
smtpd_milters = [
|
||||
"unix:/run/rspamd/rspamd-milter.sock"
|
||||
"unix:/var/run/opendkim/opendkim.sock"
|
||||
];
|
||||
|
||||
non_smtpd_milters = [
|
||||
"unix:/run/rspamd/rspamd-milter.sock"
|
||||
"unix:/var/run/opendkim/opendkim.sock"
|
||||
];
|
||||
|
||||
smtpd_relay_restrictions = [
|
||||
"permit_mynetworks"
|
||||
"permit_sasl_authenticated"
|
||||
"reject_unauth_destination"
|
||||
"reject_unauth_pipelining"
|
||||
"reject_unauth_destination"
|
||||
"reject_unknown_sender_domain"
|
||||
];
|
||||
|
||||
smtpd_sender_restrictions = [
|
||||
"check_sender_access ${mapped-file "reject_senders"}"
|
||||
"permit_mynetworks"
|
||||
"permit_sasl_authenticated"
|
||||
"reject_unknown_sender_domain"
|
||||
];
|
||||
|
||||
smtpd_recipient_restrictions = [
|
||||
"check_sender_access ${mapped-file "reject_recipients"}"
|
||||
"permit_mynetworks"
|
||||
"permit_sasl_authenticated"
|
||||
"check_policy_service unix:private/policy-spf"
|
||||
"reject_unknown_recipient_domain"
|
||||
"reject_unauth_pipelining"
|
||||
"reject_unauth_destination"
|
||||
"reject_invalid_hostname"
|
||||
"reject_non_fqdn_hostname"
|
||||
"reject_non_fqdn_sender"
|
||||
"reject_non_fqdn_recipient"
|
||||
];
|
||||
|
||||
smtpd_helo_restrictions =
|
||||
[ "permit_mynetworks" "reject_invalid_hostname" "permit" ];
|
||||
|
||||
# Handled by submission
|
||||
smtpd_tls_security_level = "may";
|
||||
|
||||
smtpd_tls_eecdh_grade = "ultra";
|
||||
|
||||
# Disable obselete protocols
|
||||
smtpd_tls_protocols =
|
||||
[ "TLSv1.2" "TLSv1.1" "!TLSv1" "!SSLv2" "!SSLv3" ];
|
||||
smtp_tls_protocols =
|
||||
[ "TLSv1.2" "TLSv1.1" "!TLSv1" "!SSLv2" "!SSLv3" ];
|
||||
smtpd_tls_mandatory_protocols =
|
||||
[ "TLSv1.2" "TLSv1.1" "!TLSv1" "!SSLv2" "!SSLv3" ];
|
||||
smtp_tls_mandatory_protocols =
|
||||
[ "TLSv1.2" "TLSv1.1" "!TLSv1" "!SSLv2" "!SSLv3" ];
|
||||
|
||||
smtp_tls_ciphers = "high";
|
||||
smtpd_tls_ciphers = "high";
|
||||
smtp_tls_mandatory_ciphers = "high";
|
||||
smtpd_tls_mandatory_ciphers = "high";
|
||||
|
||||
smtpd_tls_mandatory_exclude_ciphers =
|
||||
[ "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL" ];
|
||||
smtpd_tls_exclude_ciphers =
|
||||
[ "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL" ];
|
||||
smtp_tls_mandatory_exclude_ciphers =
|
||||
[ "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL" ];
|
||||
smtp_tls_exclude_ciphers =
|
||||
[ "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL" ];
|
||||
|
||||
tls_preempt_cipherlist = "yes";
|
||||
|
||||
smtpd_tls_auth_only = "yes";
|
||||
|
||||
smtpd_tls_loglevel = "1";
|
||||
|
||||
tls_random_source = "dev:/dev/urandom";
|
||||
};
|
||||
|
||||
submissionOptions = {
|
||||
smtpd_tls_security_level = "encrypt";
|
||||
smtpd_sasl_auth_enable = "yes";
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||
smtpd_sasl_security_options = "noanonymous";
|
||||
smtpd_sasl_local_domain = cfg.domain;
|
||||
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
||||
smtpd_sender_restrictions =
|
||||
"reject_sender_login_mismatch,reject_unknown_sender_domain";
|
||||
smtpd_recipient_restrictions =
|
||||
"reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
||||
cleanup_service_name = "submission-header-cleanup";
|
||||
};
|
||||
|
||||
masterConfig = {
|
||||
"policy-spf" = let
|
||||
policySpfFile = pkgs.writeText "policyd-spf.conf"
|
||||
(cfg.postfix.policy-spf.extraConfig
|
||||
+ (lib.optionalString cfg.debug "debugLevel = 4"));
|
||||
in {
|
||||
type = "unix";
|
||||
privileged = true;
|
||||
chroot = false;
|
||||
command = "spawn";
|
||||
args = [
|
||||
"user=nobody"
|
||||
"argv=${pkgs.pypolicyd-spf}/bin/policyd-spf"
|
||||
"${policydSpf}"
|
||||
];
|
||||
};
|
||||
"submission-header-cleanup" = let
|
||||
submissionHeaderCleanupRules =
|
||||
pkgs.writeText "submission_header_cleanup_rules" ''
|
||||
# Removes sensitive headers from mails handed in via the submission port.
|
||||
# See https://thomas-leister.de/mailserver-debian-stretch/
|
||||
# Uses "pcre" style regex.
|
||||
|
||||
/^Received:/ IGNORE
|
||||
/^X-Originating-IP:/ IGNORE
|
||||
/^X-Mailer:/ IGNORE
|
||||
/^User-Agent:/ IGNORE
|
||||
/^X-Enigmail:/ IGNORE
|
||||
'';
|
||||
in {
|
||||
type = "unix";
|
||||
private = false;
|
||||
chroot = false;
|
||||
maxproc = 0;
|
||||
command = "cleanup";
|
||||
args =
|
||||
[ "-o" "header_checks=pcre:${submissionHeaderCleanupRules}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# TODO: use blacklists
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.mail.rspamd;
|
||||
mailCfg = config.fudo.mail;
|
||||
|
||||
in {
|
||||
options.fudo.mail.rspamd = with types; {
|
||||
enable = mkEnableOption "Enable rspamd spam test server.";
|
||||
|
||||
ports = {
|
||||
metrics = mkOption {
|
||||
type = port;
|
||||
default = 7573;
|
||||
};
|
||||
controller = mkOption {
|
||||
type = port;
|
||||
default = 11334;
|
||||
};
|
||||
milter = mkOption {
|
||||
type = port;
|
||||
default = 11335;
|
||||
};
|
||||
};
|
||||
|
||||
antivirus = {
|
||||
host = mkOption {
|
||||
type = str;
|
||||
description = "Host of the ClamAV server.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "Port at which to reach ClamAV";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.prometheus.exporters.rspamd = {
|
||||
enable = true;
|
||||
listenAddress = "127.0.0.1";
|
||||
port = cfg.metrics-port;
|
||||
};
|
||||
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
|
||||
locals = {
|
||||
"milter_headers.conf".text = "extended_spam_headers = yes;";
|
||||
|
||||
"antivirus.conf".text = ''
|
||||
clamav {
|
||||
action = "reject";
|
||||
symbol = "CLAM_VIRUS";
|
||||
type = "clamav";
|
||||
log_clean = true;
|
||||
servers = "${cfg.antivirus.host}:${cfg.antivirus.port}";
|
||||
scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
overrides."milter_headers.conf".text = "extended_spam_headers = true;";
|
||||
|
||||
workers = {
|
||||
rspamd_proxy = {
|
||||
type = "rspamd_proxy";
|
||||
bindSockets = [ "localhost:${toString cfg.port}" ];
|
||||
count = 4;
|
||||
extraConfig = ''
|
||||
milter = yes;
|
||||
timeout = 120s;
|
||||
|
||||
upstream "local" {
|
||||
default = yes;
|
||||
self_scan = yes;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
controller = {
|
||||
type = "controller";
|
||||
count = 4;
|
||||
bindSockets = [ "localhost:${toString cfg.controller-port}" ];
|
||||
includes = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.mailbox" "*" {
|
||||
set "mailbox" "${1}";
|
||||
}
|
||||
|
||||
if string "${mailbox}" "Trash" {
|
||||
stop;
|
||||
}
|
||||
|
||||
if string "${mailbox}" "Junk" {
|
||||
stop
|
||||
}
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "rspamd_learn_ham" [ "${username}" ];
|
|
@ -0,0 +1,7 @@
|
|||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "rspamd_learn_spam" [ "${username}" ];
|
Loading…
Reference in New Issue