]> git.immae.eu Git - perso/Immae/Config/Nix.git/blame - systems/eldiron/mail/postfix.nix
Squash changes containing private information
[perso/Immae/Config/Nix.git] / systems / eldiron / mail / postfix.nix
CommitLineData
1a64deeb
IB
1{ lib, pkgs, config, options, ... }:
2let
3 getDomains = p: lib.mapAttrsToList (n: v: v.fqdn) (lib.filterAttrs (n: v: v.receive) p.emailPolicies);
4 bydomain = builtins.mapAttrs (n: getDomains) config.myServices.dns.zones;
5 receiving_domains = lib.flatten (builtins.attrValues bydomain);
6in
a929614f 7{
1a64deeb
IB
8 options.services.postfix.submissionOptions' = options.services.postfix.submissionOptions // {
9 type = with lib.types; attrsOf (either str (listOf str));
10 apply = builtins.mapAttrs (n: v: if builtins.isList v then builtins.concatStringsSep "," v else v);
11 };
8415083e 12 config = lib.mkIf config.myServices.mail.enable {
1a64deeb
IB
13 myServices.dns.zones."immae.eu" = with config.myServices.dns.helpers; lib.mkMerge [
14 mailMX
15 (mailCommon "immae.eu")
16 mailSend
17 {
18 # Virtual forwards and mailboxes for real users
19 emailPolicies."mail".receive = true;
20 # multi-domain generic mails:
21 # hostmaster, cron, httpd, naemon, postmaster
22 # system virtual mailboxes:
23 # devnull, printer, testconnect
24 emailPolicies."".receive = true;
25 subdomains.mail = lib.mkMerge [ (mailCommon "immae.eu") mailSend ];
26 subdomains.smtp = ips servers.eldiron.ips.main;
27
28 # DMARC reports
29 subdomains._dmarc.subdomains._report.subdomains = let
30 getDomains = p: lib.mapAttrsToList (n: v: v.fqdn) p.emailPolicies;
31 bydomain = builtins.mapAttrs (n: getDomains) config.myServices.dns.zones;
32 hostsWithMail = lib.flatten (builtins.attrValues bydomain);
33 nvpairs = builtins.map (e: { name = e; value = { TXT = [ "v=DMARC1;" ]; }; }) hostsWithMail;
34 in
35 builtins.listToAttrs nvpairs;
36 }
37 ];
38
39 myServices.chatonsProperties.hostings.mx-backup = {
40 file.datetime = "2022-08-22T01:00:00";
41 hosting = {
42 name = "MX Backup";
43 description = "Serveur e-mail secondaire";
44 logo = "https://www.postfix.org/favicon.ico";
45 website = "https://mail.immae.eu/";
46 status.level = "OK";
47 status.description = "OK";
48 registration.load = "OPEN";
49 install.type = "PACKAGE";
50 };
51 software = {
52 name = "Postfix";
53 website = "http://www.postfix.org/";
54 license.url = "http://postfix.mirrors.ovh.net/postfix-release/LICENSE";
55 license.name = "Eclipse Public license (EPL 2.0) and IBM Public License (IPL 1.0)";
56 version = pkgs.postfix.version;
57 source.url = "http://www.postfix.org/download.html";
58 };
59 };
4c4652aa
IB
60 secrets.keys = {
61 "postfix/mysql_alias_maps" = {
8415083e
IB
62 user = config.services.postfix.user;
63 group = config.services.postfix.group;
64 permissions = "0440";
65 text = ''
66 # We need to specify that option to trigger ssl connection
67 tls_ciphers = TLSv1.2
ab8f306d
IB
68 user = ${config.myEnv.mail.postfix.mysql.user}
69 password = ${config.myEnv.mail.postfix.mysql.password}
70 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
71 dbname = ${config.myEnv.mail.postfix.mysql.database}
8415083e 72 query = SELECT DISTINCT destination
418a4ed7 73 FROM forwardings
8415083e
IB
74 WHERE
75 ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s'))
76 AND active = 1
77 AND '%s' NOT IN
78 (
79 SELECT source
80 FROM forwardings_blacklisted
81 WHERE source = '%s'
82 ) UNION
83 SELECT 'devnull@immae.eu'
a929614f
IB
84 FROM forwardings_blacklisted
85 WHERE source = '%s'
8415083e 86 '';
4c4652aa
IB
87 };
88 "postfix/ldap_mailboxes" = {
8415083e
IB
89 user = config.services.postfix.user;
90 group = config.services.postfix.group;
91 permissions = "0440";
92 text = ''
22b4bd78
IB
93 server_host = ldaps://${config.myEnv.mail.dovecot.ldap.host}:636
94 search_base = ${config.myEnv.mail.dovecot.ldap.base}
95 query_filter = ${config.myEnv.mail.dovecot.ldap.postfix_mailbox_filter}
96 bind_dn = ${config.myEnv.mail.dovecot.ldap.dn}
97 bind_pw = ${config.myEnv.mail.dovecot.ldap.password}
98 result_attribute = immaePostfixAddress
99 result_format = dummy
100 version = 3
a929614f 101 '';
4c4652aa
IB
102 };
103 "postfix/mysql_sender_login_maps" = {
8415083e
IB
104 user = config.services.postfix.user;
105 group = config.services.postfix.group;
106 permissions = "0440";
107 text = ''
108 # We need to specify that option to trigger ssl connection
109 tls_ciphers = TLSv1.2
ab8f306d
IB
110 user = ${config.myEnv.mail.postfix.mysql.user}
111 password = ${config.myEnv.mail.postfix.mysql.password}
112 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
113 dbname = ${config.myEnv.mail.postfix.mysql.database}
8415083e 114 query = SELECT DISTINCT destination
418a4ed7 115 FROM forwardings
8415083e 116 WHERE
d8c20bc3
IB
117 (
118 (regex = 1 AND CONCAT(SUBSTRING_INDEX('%u', '+', 1), '@%d') REGEXP CONCAT('^',source,'$') )
119 OR
120 (regex = 0 AND source = CONCAT(SUBSTRING_INDEX('%u', '+', 1), '@%d'))
121 )
8415083e 122 AND active = 1
d8c20bc3 123 UNION SELECT CONCAT(SUBSTRING_INDEX('%u', '+', 1), '@%d') AS destination
8415083e 124 '';
4c4652aa
IB
125 };
126 "postfix/mysql_sender_relays_maps" = {
87a8bffd
IB
127 user = config.services.postfix.user;
128 group = config.services.postfix.group;
129 permissions = "0440";
130 text = ''
131 # We need to specify that option to trigger ssl connection
132 tls_ciphers = TLSv1.2
133 user = ${config.myEnv.mail.postfix.mysql.user}
134 password = ${config.myEnv.mail.postfix.mysql.password}
135 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
136 dbname = ${config.myEnv.mail.postfix.mysql.database}
137 # INSERT INTO sender_relays
138 # (`from`, owner, relay, login, password, regex, active)
139 # VALUES
140 # ( 'sender@otherhost.org'
141 # , 'me@mail.immae.eu'
142 # , '[otherhost.org]:587'
143 # , 'otherhostlogin'
144 # , AES_ENCRYPT('otherhostpassword', '${config.myEnv.mail.postfix.mysql.password_encrypt}')
145 # , '0'
146 # , '1');
147
148 query = SELECT DISTINCT `owner`
149 FROM sender_relays
150 WHERE
151 ((regex = 1 AND '%s' REGEXP CONCAT('^',`from`,'$') ) OR (regex = 0 AND `from` = '%s'))
152 AND active = 1
153 '';
4c4652aa
IB
154 };
155 "postfix/mysql_sender_relays_hosts" = {
87a8bffd
IB
156 user = config.services.postfix.user;
157 group = config.services.postfix.group;
158 permissions = "0440";
159 text = ''
160 # We need to specify that option to trigger ssl connection
161 tls_ciphers = TLSv1.2
162 user = ${config.myEnv.mail.postfix.mysql.user}
163 password = ${config.myEnv.mail.postfix.mysql.password}
164 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
165 dbname = ${config.myEnv.mail.postfix.mysql.database}
166
167 query = SELECT DISTINCT relay
168 FROM sender_relays
169 WHERE
170 ((regex = 1 AND '%s' REGEXP CONCAT('^',`from`,'$') ) OR (regex = 0 AND `from` = '%s'))
171 AND active = 1
172 '';
4c4652aa
IB
173 };
174 "postfix/mysql_sender_relays_creds" = {
87a8bffd
IB
175 user = config.services.postfix.user;
176 group = config.services.postfix.group;
177 permissions = "0440";
178 text = ''
179 # We need to specify that option to trigger ssl connection
180 tls_ciphers = TLSv1.2
181 user = ${config.myEnv.mail.postfix.mysql.user}
182 password = ${config.myEnv.mail.postfix.mysql.password}
183 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
184 dbname = ${config.myEnv.mail.postfix.mysql.database}
185
186 query = SELECT DISTINCT CONCAT(`login`, ':', AES_DECRYPT(`password`, '${config.myEnv.mail.postfix.mysql.password_encrypt}'))
187 FROM sender_relays
188 WHERE
189 ((regex = 1 AND '%s' REGEXP CONCAT('^',`from`,'$') ) OR (regex = 0 AND `from` = '%s'))
190 AND active = 1
191 '';
4c4652aa
IB
192 };
193 "postfix/ldap_ejabberd_users_immae_fr" = {
5b53d86f
IB
194 user = config.services.postfix.user;
195 group = config.services.postfix.group;
196 permissions = "0440";
197 text = ''
198 server_host = ldaps://${config.myEnv.jabber.ldap.host}:636
199 search_base = ${config.myEnv.jabber.ldap.base}
200 query_filter = ${config.myEnv.jabber.postfix_user_filter}
201 domain = immae.fr
202 bind_dn = ${config.myEnv.jabber.ldap.dn}
203 bind_pw = ${config.myEnv.jabber.ldap.password}
204 result_attribute = immaeXmppUid
205 result_format = ejabberd@localhost
206 version = 3
207 '';
4c4652aa 208 };
1a64deeb 209 };
a929614f 210
8415083e 211 networking.firewall.allowedTCPPorts = [ 25 465 587 ];
a929614f 212
31d99b75
IB
213 users.users.postfixscripts = {
214 group = "keys";
215 uid = config.ids.uids.postfixscripts;
216 description = "Postfix scripts user";
217 };
8415083e
IB
218 users.users."${config.services.postfix.user}".extraGroups = [ "keys" ];
219 services.filesWatcher.postfix = {
220 restart = true;
221 paths = [
222 config.secrets.fullPaths."postfix/mysql_alias_maps"
22b4bd78 223 config.secrets.fullPaths."postfix/ldap_mailboxes"
8415083e 224 config.secrets.fullPaths."postfix/mysql_sender_login_maps"
5b53d86f 225 config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"
8415083e
IB
226 ];
227 };
228 services.postfix = {
2a61e9da 229 extraAliases = let
1a64deeb 230 testmail = pkgs.writeScript "testmail" ''
2a61e9da 231 #! ${pkgs.stdenv.shell}
1a64deeb
IB
232 ${pkgs.coreutils}/bin/touch \
233 "/var/lib/naemon/checks/email/$(${pkgs.procmail}/bin/formail -x To: | ${pkgs.coreutils}/bin/tr -d ' <>')"
2a61e9da 234 '';
1a64deeb
IB
235 in
236 ''testmail: "|${testmail}"'';
8415083e 237 mapFiles = let
2a61e9da 238 virtual_map = {
71a2425e
IB
239 virtual = let
240 cfg = config.myEnv.monitoring.email_check.eldiron;
241 address = "${cfg.mail_address}@${cfg.mail_domain}";
1a64deeb
IB
242 aliases = config.myEnv.mail.postfix.common_aliases;
243 admins = builtins.concatStringsSep "," config.myEnv.mail.postfix.admins;
71a2425e 244 in pkgs.writeText "postfix-virtual" (
2a61e9da 245 builtins.concatStringsSep "\n" (
1a64deeb
IB
246 [ "${address} testmail@localhost"
247 ] ++
248 map (a: "${a} ${admins}") config.myEnv.mail.postfix.other_aliases
249 ++ lib.lists.flatten (
250 map (domain:
251 map (alias: "${alias}@${domain} ${admins}") aliases
252 ) receiving_domains
253 )
254 ));
2a61e9da 255 };
deca5e9b 256 sasl_access = {
ef0a9217
IB
257 host_sender_login = with lib.attrsets; let
258 addresses = zipAttrs (lib.flatten (mapAttrsToList
259 (n: v: (map (e: { "${e}" = "${n}@immae.eu"; }) v.emails)) config.myEnv.servers));
1a64deeb 260 aliases = config.myEnv.mail.postfix.common_aliases;
ef0a9217 261 joined = builtins.concatStringsSep ",";
1a64deeb 262 admins = joined config.myEnv.mail.postfix.admins;
ef0a9217 263 in pkgs.writeText "host-sender-login"
1a64deeb
IB
264 (builtins.concatStringsSep "\n" (
265 mapAttrsToList (n: v: "${n} ${joined v}") addresses
266 ++ lib.lists.flatten (
267 map (domain:
268 map (alias: "${alias}@${domain} ${admins}") aliases
269 ) receiving_domains
270 )
271 ++ map (a: "${a} ${admins}") config.myEnv.mail.postfix.other_aliases
272 ));
deca5e9b 273 };
8415083e 274 in
1a64deeb 275 virtual_map // sasl_access;
8415083e
IB
276 config = {
277 ### postfix module overrides
278 readme_directory = "${pkgs.postfix}/share/postfix/doc";
279 smtp_tls_CAfile = lib.mkForce "";
280 smtp_tls_cert_file = lib.mkForce "";
281 smtp_tls_key_file = lib.mkForce "";
a929614f 282
8415083e 283 message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited"
2a61e9da 284 mailbox_size_limit = "1073741825"; # Workaround, local delivered mails should all go through scripts
8415083e 285 alias_database = "\$alias_maps";
a929614f 286
31d99b75
IB
287 ### Aliases scripts user
288 default_privs = "postfixscripts";
289
8415083e 290 ### Virtual mailboxes config
418a4ed7
IB
291 virtual_alias_maps = [
292 "hash:/etc/postfix/virtual"
293 "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"
294 "ldap:${config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"}"
295 ];
1a64deeb 296 virtual_mailbox_domains = receiving_domains;
418a4ed7 297 virtual_mailbox_maps = [
22b4bd78 298 "ldap:${config.secrets.fullPaths."postfix/ldap_mailboxes"}"
418a4ed7 299 ];
8415083e
IB
300 dovecot_destination_recipient_limit = "1";
301 virtual_transport = "dovecot";
a929614f 302
8415083e 303 ### Relay domains
8415083e 304 smtpd_relay_restrictions = [
8415083e 305 "defer_unauth_destination"
1a64deeb 306 ];
a929614f 307
8415083e
IB
308 ### Additional smtpd configuration
309 smtpd_tls_received_header = "yes";
310 smtpd_tls_loglevel = "1";
a929614f 311
8415083e
IB
312 ### Email sending configuration
313 smtp_tls_security_level = "may";
314 smtp_tls_loglevel = "1";
a929614f 315
8415083e 316 ### Force ip bind for smtp
1a64deeb 317 smtp_bind_address = builtins.head config.hostEnv.ips.main.ip4;
619e4f46 318 smtp_bind_address6 = builtins.head config.hostEnv.ips.main.ip6;
a929614f 319
87a8bffd 320 # Use some relays when authorized senders are not myself
1a64deeb
IB
321 smtp_sasl_mechanism_filter = [
322 "plain"
323 "login"
324 ]; # GSSAPI Not correctly supported by postfix
87a8bffd 325 smtp_sasl_auth_enable = "yes";
1a64deeb
IB
326 smtp_sasl_password_maps = [
327 "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_creds"}"
328 ];
87a8bffd
IB
329 smtp_sasl_security_options = "noanonymous";
330 smtp_sender_dependent_authentication = "yes";
1a64deeb
IB
331 sender_dependent_relayhost_maps = [
332 "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_hosts"}"
333 ];
a929614f 334
8415083e
IB
335 ### opendkim, opendmarc, openarc milters
336 non_smtpd_milters = [
337 "unix:${config.myServices.mail.milters.sockets.opendkim}"
8415083e
IB
338 ];
339 smtpd_milters = [
340 "unix:${config.myServices.mail.milters.sockets.opendkim}"
8415083e 341 "unix:${config.myServices.mail.milters.sockets.openarc}"
619e4f46 342 "unix:${config.myServices.mail.milters.sockets.opendmarc}"
a929614f 343 ];
5153eb54
IB
344
345 smtp_use_tls = true;
346 smtpd_use_tls = true;
1a64deeb
IB
347 smtpd_tls_chain_files = [
348 "/var/lib/acme/mail/full.pem"
349 "/var/lib/acme/mail-rsa/full.pem"
350 ];
6ea94404
IB
351
352 maximal_queue_lifetime = "6w";
353 bounce_queue_lifetime = "6w";
a929614f 354 };
8415083e
IB
355 enable = true;
356 enableSmtp = true;
357 enableSubmission = true;
1a64deeb
IB
358 submissionOptions = config.services.postfix.submissionOptions';
359 submissionOptions' = {
87a8bffd
IB
360 # Don’t use "long form", only commas (cf
361 # http://www.postfix.org/master.5.html long form is not handled
362 # well by the submission function)
8415083e
IB
363 smtpd_tls_security_level = "encrypt";
364 smtpd_sasl_auth_enable = "yes";
365 smtpd_tls_auth_only = "yes";
366 smtpd_sasl_tls_security_options = "noanonymous";
367 smtpd_sasl_type = "dovecot";
368 smtpd_sasl_path = "private/auth";
369 smtpd_reject_unlisted_recipient = "no";
1a64deeb
IB
370 smtpd_client_restrictions = [
371 "permit_sasl_authenticated"
372 "reject"
373 ];
374 smtpd_relay_restrictions = [
375 "permit_sasl_authenticated"
376 "reject"
377 ];
8415083e 378 # Refuse to send e-mails with a From that is not handled
1a64deeb
IB
379 smtpd_sender_restrictions = [
380 "reject_sender_login_mismatch"
381 "reject_unlisted_sender"
382 "permit_sasl_authenticated,reject"
383 ];
384 smtpd_sender_login_maps = [
87a8bffd
IB
385 "hash:/etc/postfix/host_sender_login"
386 "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_maps"}"
387 "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}"
388 ];
1a64deeb
IB
389 smtpd_recipient_restrictions = [
390 "permit_sasl_authenticated"
391 "reject"
392 ];
8415083e 393 milter_macro_daemon_name = "ORIGINATING";
1a64deeb 394 smtpd_milters = [
45730653
IB
395 # FIXME: put it back when opensmtpd is upgraded and able to
396 # rewrite the from header
397 #"unix:/run/milter_verify_from/verify_from.sock"
398 "unix:${config.myServices.mail.milters.sockets.opendkim}"
399 ];
8415083e 400 };
8415083e
IB
401 destination = ["localhost"];
402 # This needs to reverse DNS
619e4f46 403 hostname = config.hostEnv.fqdn;
8415083e 404 setSendmail = true;
8415083e
IB
405 recipientDelimiter = "+";
406 masterConfig = {
407 submissions = {
408 type = "inet";
409 private = false;
410 command = "smtpd";
411 args = ["-o" "smtpd_tls_wrappermode=yes" ] ++ (let
412 mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
413 in lib.concatLists (lib.mapAttrsToList mkKeyVal config.services.postfix.submissionOptions)
414 );
415 };
416 dovecot = {
417 type = "unix";
418 privileged = true;
419 chroot = false;
420 command = "pipe";
421 args = let
422 # rspamd could be used as a milter, but then it cannot apply
423 # its checks "per user" (milter is not yet dispatched to
424 # users), so we wrap dovecot-lda inside rspamc per recipient
425 # here.
dc6d3af9
IB
426 rspamc_dovecot = pkgs.writeScriptBin "rspamc_dovecot" ''
427 #! ${pkgs.stdenv.shell}
1a64deeb 428 set -o pipefail
dc6d3af9
IB
429 sender="$1"
430 original_recipient="$2"
431 user="$3"
432
433 ${pkgs.coreutils}/bin/cat - | \
1a64deeb 434 ${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d "$user" --mime | \
dc6d3af9 435 ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f "$sender" -a "$original_recipient" -d "$user"
1a64deeb
IB
436 if echo ''${PIPESTATUS[@]} | ${pkgs.gnugrep}/bin/grep -qE '^[0 ]+$'; then
437 exit 0
438 else
439 # src/global/sys_exits.h to retry
440 exit 75
441 fi
dc6d3af9 442 '';
8415083e 443 in [
fb7611c1 444 "flags=ODRhu" "user=vhost:vhost"
dc6d3af9 445 "argv=${rspamc_dovecot}/bin/rspamc_dovecot \${sender} \${original_recipient} \${user}@\${nexthop}"
8415083e
IB
446 ];
447 };
448 };
a929614f 449 };
5400b9b6 450 security.acme.certs."mail" = {
8415083e
IB
451 postRun = ''
452 systemctl restart postfix.service
453 '';
1a64deeb 454 extraDomainNames = [ "smtp.immae.eu" ];
514f9ec3
IB
455 };
456 security.acme.certs."mail-rsa" = {
457 postRun = ''
458 systemctl restart postfix.service
459 '';
1a64deeb 460 extraDomainNames = [ "smtp.immae.eu" ];
a929614f 461 };
71a2425e
IB
462 system.activationScripts.testmail = {
463 deps = [ "users" ];
464 text = let
465 allCfg = config.myEnv.monitoring.email_check;
466 cfg = allCfg.eldiron;
467 reverseTargets = builtins.attrNames (lib.attrsets.filterAttrs (k: v: builtins.elem "eldiron" v.targets) allCfg);
468 to_email = cfg': host':
469 let sep = if lib.strings.hasInfix "+" cfg'.mail_address then "_" else "+";
470 in "${cfg'.mail_address}${sep}${host'}@${cfg'.mail_domain}";
471 mails_to_receive = builtins.concatStringsSep " " (map (to_email cfg) reverseTargets);
472 in ''
31d99b75 473 install -m 0555 -o postfixscripts -g keys -d /var/lib/naemon/checks/email
71a2425e
IB
474 for f in ${mails_to_receive}; do
475 if [ ! -f /var/lib/naemon/checks/email/$f ]; then
31d99b75 476 install -m 0644 -o postfixscripts -g keys /dev/null -T /var/lib/naemon/checks/email/$f
71a2425e
IB
477 touch -m -d @0 /var/lib/naemon/checks/email/$f
478 fi
479 done
480 '';
481 };
850adcf4 482 systemd.services.postfix.serviceConfig.Slice = "mail.slice";
1a64deeb
IB
483
484 myServices.monitoring.fromMasterObjects.service = [
485 {
486 service_description = "postfix SSL is up to date";
487 host_name = config.hostEnv.fqdn;
488 use = "external-service";
489 check_command = "check_smtp";
490
491 servicegroups = "webstatus-ssl";
492 _webstatus_name = "SMTP";
493 _webstatus_url = "smtp.immae.eu";
494 }
495 ];
a929614f
IB
496 };
497}