stockholm/krebs/3modules/exim-smarthost.nix

291 lines
8.1 KiB
Nix

{ config, pkgs, lib, ... }:
with import <stockholm/lib>;
let
cfg = config.krebs.exim-smarthost;
out = {
options.krebs.exim-smarthost = api;
config = lib.mkIf cfg.enable imp;
};
api = {
enable = mkEnableOption "krebs.exim-smarthost";
authenticators = mkOption {
type = types.attrsOf types.str;
default = {};
};
dkim = mkOption {
type = types.listOf (types.submodule ({ config, ... }: {
options = {
domain = mkOption {
type = types.str;
};
private_key = mkOption {
type = types.secret-file;
default = {
path = "/run/krebs.secret/${config.domain}.dkim_private_key";
owner.name = "exim";
source-path = toString <secrets> + "/${config.domain}.dkim.priv";
};
};
selector = mkOption {
type = types.str;
default = "default";
};
};
}));
default = [];
};
internet-aliases = mkOption {
type = types.listOf (types.submodule ({
options = {
from = mkOption {
type = types.str; # TODO e-mail address
};
to = mkOption {
type = types.str; # TODO e-mail address / TODO listOf
};
};
}));
};
local_domains = mkOption {
type = with types; listOf hostname;
default = unique (["localhost" cfg.primary_hostname] ++ config.krebs.build.host.nets.retiolum.aliases);
};
relay_from_hosts = mkOption {
type = with types; listOf str;
default = [];
apply = xs: ["127.0.0.1" "::1"] ++ xs;
};
relay_to_domains = mkOption {
# TODO hostname with wildcards
type = with types; listOf str;
default = [
"*.r"
];
};
primary_hostname = mkOption {
type = types.str;
default = let x = "${config.krebs.build.host.name}.r"; in
assert elem x config.krebs.build.host.nets.retiolum.aliases;
x;
};
sender_domains = mkOption {
type = with types; listOf str;
default = [];
};
ssl_cert = mkOption {
type = types.nullOr types.str;
default = null;
};
ssl_key = mkOption {
type = types.nullOr types.str;
default = null;
};
system-aliases = mkOption {
type = types.listOf (types.submodule ({
options = {
from = mkOption {
type = types.str; # TODO e-mail address
};
to = mkOption {
type = types.str; # TODO e-mail address / TODO listOf
};
};
}));
};
};
imp = {
krebs.secret.files = listToAttrs (flip map cfg.dkim (dkim: {
name = "exim.dkim_private_key/${dkim.domain}";
value = dkim.private_key;
}));
systemd.services = mkIf (cfg.dkim != []) {
exim = {
after = [ "secret.service" ];
requires = [ "secret.service" ];
};
};
krebs.exim = {
enable = true;
config = ''
keep_environment =
primary_hostname = ${cfg.primary_hostname}
# HOST_REDIR contains the real destinations for "local_domains".
#HOST_REDIR = /etc/exim4/host_redirect
# Domains not listed in local_domains need to be deliverable remotely.
# XXX We abuse local_domains to mean "domains, we're the gateway for".
domainlist local_domains = ${concatStringsSep ":" cfg.local_domains}
domainlist relay_to_domains = ${concatStringsSep ":" cfg.relay_to_domains}
hostlist relay_from_hosts = <;${concatStringsSep ";" cfg.relay_from_hosts}
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
never_users = root
host_lookup = *
rfc1413_hosts = *
rfc1413_query_timeout = 5s
log_selector = -queue_run +address_rewrite +all_parents +queue_time
log_file_path = syslog
syslog_timestamp = false
syslog_duplication = false
${optionalString (cfg.ssl_cert != null) "tls_certificate = ${cfg.ssl_cert}"}
${optionalString (cfg.ssl_key != null) "tls_privatekey = ${cfg.ssl_key}"}
tls_advertise_hosts =${optionalString (cfg.ssl_cert != null) " *"}
begin acl
acl_check_rcpt:
deny
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
message = restricted characters in address
accept
recipients = lsearch*@;${lsearch.internet-aliases}
accept
authenticated = *
control = dkim_disable_verify
control = submission
accept
control = dkim_disable_verify
control = submission
hosts = +relay_from_hosts
accept
domains = +local_domains : +relay_to_domains
deny
message = relay not permitted
acl_check_data:
warn
sender_domains = ${concatStringsSep ":" cfg.sender_domains}
set acl_m_special_dom = $sender_address_domain
accept
begin routers
# feature RETIOLUM_MAIL
retiolum:
debug_print = "R: retiolum for $local_part@$domain"
driver = manualroute
domains = ! +local_domains : +relay_to_domains
transport = retiolum_smtp
route_list = ^.* $0 byname
no_more
internet_aliases:
debug_print = "R: internet_aliases for $local_part@$domain"
driver = redirect
data = ''${lookup{$local_part@$domain}lsearch*@{${lsearch.internet-aliases}}}
dnslookup:
debug_print = "R: dnslookup for $local_part@$domain"
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
system_aliases:
debug_print = "R: system_aliases for $local_part@$domain"
driver = redirect
data = ''${lookup{$local_part}lsearch{${lsearch.system-aliases}}}
local_user:
debug_print = "R: local_user for $local_part@$domain"
driver = accept
check_local_user
transport = home_maildir
cannot_route_message = Unknown user
begin transports
retiolum_smtp:
driver = smtp
retry_include_ip_address = false
remote_smtp:
driver = smtp
${optionalString (cfg.dkim != []) (indent ''
dkim_canon = relaxed
dkim_domain = $sender_address_domain
dkim_private_key = ''${lookup{$sender_address_domain}lsearch{${lsearch.dkim_private_key}}}
dkim_selector = ''${lookup{$sender_address_domain}lsearch{${lsearch.dkim_selector}}}
'')}
helo_data = ''${if eq{$acl_m_special_dom}{} \
{$primary_hostname} \
{$acl_m_special_dom} }
home_maildir:
driver = appendfile
maildir_format
maildir_use_size_file
directory = $home/Mail
directory_mode = 0700
delivery_date_add
envelope_to_add
return_path_add
begin retry
${concatMapStringsSep "\n" (k: "${k} * F,42d,1m") cfg.relay_to_domains}
${concatMapStringsSep "\n" (k: "${k} * F,42d,1m")
# TODO don't include relay_to_domains
(map (getAttr "from") cfg.internet-aliases)}
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
begin rewrite
begin authenticators
${concatStringsSep "\n" (mapAttrsToList (name: text: ''
${name}:
${indent text}
'') cfg.authenticators)}
'';
};
};
lsearch = mapAttrs (name: set: toFile name (to-lsearch set)) ({
inherit (cfg) internet-aliases;
inherit (cfg) system-aliases;
} // optionalAttrs (cfg.dkim != []) {
dkim_private_key = flip map cfg.dkim (dkim: {
from = dkim.domain;
to = dkim.private_key.path;
});
dkim_selector = flip map cfg.dkim (dkim: {
from = dkim.domain;
to = dkim.selector;
});
});
to-lsearch = concatMapStrings ({ from, to, ... }: "${from}: ${to}\n");
in out