2015-08-14 13:48:17 +00:00
|
|
|
|
{ config, pkgs, lib, ... }:
|
|
|
|
|
|
2016-10-20 18:54:38 +00:00
|
|
|
|
with import <stockholm/lib>;
|
2015-08-14 13:48:17 +00:00
|
|
|
|
let
|
|
|
|
|
cfg = config.krebs.exim-smarthost;
|
|
|
|
|
|
|
|
|
|
out = {
|
|
|
|
|
options.krebs.exim-smarthost = api;
|
2016-02-14 15:43:44 +00:00
|
|
|
|
config = lib.mkIf cfg.enable imp;
|
2015-08-14 13:48:17 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
api = {
|
|
|
|
|
enable = mkEnableOption "krebs.exim-smarthost";
|
|
|
|
|
|
2022-12-07 19:03:15 +00:00
|
|
|
|
enableSPFVerification = mkEnableOption "SPF verification";
|
|
|
|
|
|
2016-07-23 17:16:22 +00:00
|
|
|
|
authenticators = mkOption {
|
|
|
|
|
type = types.attrsOf types.str;
|
|
|
|
|
default = {};
|
|
|
|
|
};
|
|
|
|
|
|
2016-03-05 18:51:29 +00:00
|
|
|
|
dkim = mkOption {
|
2016-03-05 19:28:08 +00:00
|
|
|
|
type = types.listOf (types.submodule ({ config, ... }: {
|
2016-03-05 18:51:29 +00:00
|
|
|
|
options = {
|
|
|
|
|
domain = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
};
|
|
|
|
|
private_key = mkOption {
|
2021-12-24 08:22:41 +00:00
|
|
|
|
type = types.absolute-pathname;
|
|
|
|
|
default = toString <secrets> + "/${config.domain}.dkim.priv";
|
2021-11-08 02:53:14 +00:00
|
|
|
|
defaultText = "‹secrets/‹domain›.dkim.priv›";
|
2016-03-05 18:51:29 +00:00
|
|
|
|
};
|
|
|
|
|
selector = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = "default";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}));
|
2016-03-05 19:28:08 +00:00
|
|
|
|
default = [];
|
2016-03-05 18:51:29 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-03-09 08:09:37 +00:00
|
|
|
|
extraRouters = mkOption {
|
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
|
2015-08-14 13:48:17 +00:00
|
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
2016-02-21 20:51:11 +00:00
|
|
|
|
local_domains = mkOption {
|
|
|
|
|
type = with types; listOf hostname;
|
2017-03-04 18:59:36 +00:00
|
|
|
|
default = unique (["localhost" cfg.primary_hostname] ++ config.krebs.build.host.nets.retiolum.aliases);
|
2016-02-21 20:51:11 +00:00
|
|
|
|
};
|
|
|
|
|
|
2015-08-14 13:48:17 +00:00
|
|
|
|
relay_from_hosts = mkOption {
|
|
|
|
|
type = with types; listOf str;
|
|
|
|
|
default = [];
|
2016-02-21 20:51:11 +00:00
|
|
|
|
apply = xs: ["127.0.0.1" "::1"] ++ xs;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
relay_to_domains = mkOption {
|
|
|
|
|
# TODO hostname with wildcards
|
|
|
|
|
type = with types; listOf str;
|
|
|
|
|
default = [
|
|
|
|
|
"*.r"
|
|
|
|
|
];
|
2015-08-14 13:48:17 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
primary_hostname = mkOption {
|
|
|
|
|
type = types.str;
|
2016-02-21 20:51:11 +00:00
|
|
|
|
default = let x = "${config.krebs.build.host.name}.r"; in
|
|
|
|
|
assert elem x config.krebs.build.host.nets.retiolum.aliases;
|
|
|
|
|
x;
|
2015-08-14 13:48:17 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
sender_domains = mkOption {
|
|
|
|
|
type = with types; listOf str;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
|
2016-07-23 17:16:41 +00:00
|
|
|
|
ssl_cert = mkOption {
|
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ssl_key = mkOption {
|
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
|
2015-08-14 13:48:17 +00:00
|
|
|
|
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 = {
|
2022-12-29 12:44:45 +00:00
|
|
|
|
krebs.systemd.services.exim.restartIfCredentialsChange = true;
|
2021-12-24 08:22:41 +00:00
|
|
|
|
systemd.services.exim.serviceConfig.LoadCredential =
|
|
|
|
|
map (dkim: "${dkim.domain}.dkim_private_key:${dkim.private_key}") cfg.dkim;
|
2016-04-26 23:54:58 +00:00
|
|
|
|
krebs.exim = {
|
2015-08-14 13:48:17 +00:00
|
|
|
|
enable = true;
|
2019-06-22 10:55:16 +00:00
|
|
|
|
config = /* exim */ ''
|
2021-12-24 08:22:41 +00:00
|
|
|
|
keep_environment = CREDENTIALS_DIRECTORY
|
2016-03-05 10:46:30 +00:00
|
|
|
|
|
2015-08-14 13:48:17 +00:00
|
|
|
|
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".
|
2016-02-21 20:51:11 +00:00
|
|
|
|
domainlist local_domains = ${concatStringsSep ":" cfg.local_domains}
|
|
|
|
|
domainlist relay_to_domains = ${concatStringsSep ":" cfg.relay_to_domains}
|
2022-12-05 19:08:50 +00:00
|
|
|
|
domainlist sender_domains = ${concatStringsSep ":" cfg.sender_domains}
|
2016-02-21 20:51:11 +00:00
|
|
|
|
hostlist relay_from_hosts = <;${concatStringsSep ";" cfg.relay_from_hosts}
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
2022-12-07 18:25:46 +00:00
|
|
|
|
acl_smtp_data = acl_check_data
|
2022-12-07 18:51:13 +00:00
|
|
|
|
acl_smtp_mail = acl_check_mail
|
|
|
|
|
acl_smtp_rcpt = acl_check_rcpt
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2016-07-23 17:16:41 +00:00
|
|
|
|
${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) " *"}
|
2016-04-12 12:26:37 +00:00
|
|
|
|
|
2015-08-14 13:48:17 +00:00
|
|
|
|
begin acl
|
|
|
|
|
|
|
|
|
|
acl_check_rcpt:
|
2017-10-15 22:45:27 +00:00
|
|
|
|
deny
|
|
|
|
|
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
|
|
|
|
|
message = restricted characters in address
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
2017-10-15 22:45:27 +00:00
|
|
|
|
accept
|
|
|
|
|
recipients = lsearch*@;${lsearch.internet-aliases}
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
2017-10-15 22:45:27 +00:00
|
|
|
|
accept
|
|
|
|
|
authenticated = *
|
|
|
|
|
control = dkim_disable_verify
|
|
|
|
|
control = submission
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
2017-10-15 22:45:27 +00:00
|
|
|
|
accept
|
|
|
|
|
control = dkim_disable_verify
|
|
|
|
|
control = submission
|
|
|
|
|
hosts = +relay_from_hosts
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
|
|
|
|
accept
|
2017-10-15 22:45:27 +00:00
|
|
|
|
domains = +local_domains : +relay_to_domains
|
|
|
|
|
|
|
|
|
|
deny
|
|
|
|
|
message = relay not permitted
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
acl_check_data:
|
|
|
|
|
warn
|
2022-12-05 19:08:50 +00:00
|
|
|
|
sender_domains = +sender_domains
|
2015-08-14 13:48:17 +00:00
|
|
|
|
set acl_m_special_dom = $sender_address_domain
|
|
|
|
|
|
|
|
|
|
accept
|
|
|
|
|
|
2022-12-07 18:51:13 +00:00
|
|
|
|
acl_check_mail:
|
2022-12-07 19:03:15 +00:00
|
|
|
|
${if cfg.enableSPFVerification then indent /* exim */ ''
|
|
|
|
|
accept
|
|
|
|
|
authenticated = *
|
|
|
|
|
accept
|
|
|
|
|
hosts = +relay_from_hosts
|
|
|
|
|
deny
|
|
|
|
|
spf = fail : softfail
|
|
|
|
|
log_message = spf=$spf_result
|
|
|
|
|
message = SPF validation failed: \
|
|
|
|
|
$sender_host_address is not allowed to send mail from \
|
|
|
|
|
''${if def:sender_address_domain\
|
|
|
|
|
{$sender_address_domain}\
|
|
|
|
|
{$sender_helo_name}}
|
|
|
|
|
deny
|
|
|
|
|
spf = permerror
|
|
|
|
|
log_message = spf=$spf_result
|
|
|
|
|
message = SPF validation failed: \
|
|
|
|
|
syntax error in SPF record(s) for \
|
|
|
|
|
''${if def:sender_address_domain\
|
|
|
|
|
{$sender_address_domain}\
|
|
|
|
|
{$sender_helo_name}}
|
|
|
|
|
defer
|
|
|
|
|
spf = temperror
|
|
|
|
|
log_message = spf=$spf_result; deferred
|
|
|
|
|
message = temporary error during SPF validation; \
|
|
|
|
|
please try again later
|
|
|
|
|
warn
|
|
|
|
|
spf = none : neutral
|
|
|
|
|
log_message = spf=$spf_result
|
|
|
|
|
accept
|
|
|
|
|
add_header = $spf_received
|
|
|
|
|
'' else indent /* exim */ ''
|
|
|
|
|
accept
|
|
|
|
|
''}
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
|
|
|
|
begin routers
|
|
|
|
|
|
|
|
|
|
# feature RETIOLUM_MAIL
|
|
|
|
|
retiolum:
|
|
|
|
|
debug_print = "R: retiolum for $local_part@$domain"
|
|
|
|
|
driver = manualroute
|
2016-02-21 20:51:11 +00:00
|
|
|
|
domains = ! +local_domains : +relay_to_domains
|
2015-08-14 13:48:17 +00:00
|
|
|
|
transport = retiolum_smtp
|
|
|
|
|
route_list = ^.* $0 byname
|
|
|
|
|
no_more
|
|
|
|
|
|
|
|
|
|
internet_aliases:
|
|
|
|
|
debug_print = "R: internet_aliases for $local_part@$domain"
|
|
|
|
|
driver = redirect
|
2016-05-21 07:27:55 +00:00
|
|
|
|
data = ''${lookup{$local_part@$domain}lsearch*@{${lsearch.internet-aliases}}}
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2023-03-09 08:09:37 +00:00
|
|
|
|
${lib.optionalString (cfg.extraRouters != null) cfg.extraRouters}
|
|
|
|
|
|
2015-08-14 13:48:17 +00:00
|
|
|
|
begin transports
|
|
|
|
|
|
|
|
|
|
retiolum_smtp:
|
|
|
|
|
driver = smtp
|
|
|
|
|
retry_include_ip_address = false
|
|
|
|
|
|
|
|
|
|
remote_smtp:
|
|
|
|
|
driver = smtp
|
2019-06-22 10:55:16 +00:00
|
|
|
|
${optionalString (cfg.dkim != []) (indent /* exim */ ''
|
2016-03-05 19:28:08 +00:00
|
|
|
|
dkim_canon = relaxed
|
|
|
|
|
dkim_domain = $sender_address_domain
|
2021-12-24 08:22:41 +00:00
|
|
|
|
dkim_private_key = ''${lookup{$sender_address_domain.dkim_private_key}dsearch,ret=full{''${env{CREDENTIALS_DIRECTORY}{$value}fail}}}
|
2016-03-05 19:28:08 +00:00
|
|
|
|
dkim_selector = ''${lookup{$sender_address_domain}lsearch{${lsearch.dkim_selector}}}
|
2021-12-24 09:19:13 +00:00
|
|
|
|
dkim_strict = true
|
2016-10-27 11:31:12 +00:00
|
|
|
|
'')}
|
2015-08-14 13:48:17 +00:00
|
|
|
|
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
|
2016-02-21 20:51:11 +00:00
|
|
|
|
${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
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
|
|
|
|
begin rewrite
|
|
|
|
|
begin authenticators
|
2019-06-22 10:55:16 +00:00
|
|
|
|
${concatStringsSep "\n" (mapAttrsToList (name: text: /* exim */ ''
|
2016-07-23 17:16:22 +00:00
|
|
|
|
${name}:
|
|
|
|
|
${indent text}
|
|
|
|
|
'') cfg.authenticators)}
|
2015-08-14 13:48:17 +00:00
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2016-03-05 19:28:08 +00:00
|
|
|
|
lsearch = mapAttrs (name: set: toFile name (to-lsearch set)) ({
|
2015-08-14 13:48:17 +00:00
|
|
|
|
inherit (cfg) internet-aliases;
|
|
|
|
|
inherit (cfg) system-aliases;
|
2016-03-05 19:28:08 +00:00
|
|
|
|
} // optionalAttrs (cfg.dkim != []) {
|
|
|
|
|
dkim_selector = flip map cfg.dkim (dkim: {
|
|
|
|
|
from = dkim.domain;
|
|
|
|
|
to = dkim.selector;
|
|
|
|
|
});
|
|
|
|
|
});
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
2016-03-05 19:31:59 +00:00
|
|
|
|
to-lsearch = concatMapStrings ({ from, to, ... }: "${from}: ${to}\n");
|
2015-08-14 13:48:17 +00:00
|
|
|
|
|
2016-03-05 18:51:29 +00:00
|
|
|
|
in out
|