summaryrefslogtreecommitdiff
path: root/hosts/surtr/email/default.nix
diff options
context:
space:
mode:
Diffstat (limited to 'hosts/surtr/email/default.nix')
-rw-r--r--hosts/surtr/email/default.nix182
1 files changed, 98 insertions, 84 deletions
diff --git a/hosts/surtr/email/default.nix b/hosts/surtr/email/default.nix
index 0a42b808..b0e95a0e 100644
--- a/hosts/surtr/email/default.nix
+++ b/hosts/surtr/email/default.nix
@@ -124,19 +124,20 @@ in {
124 services.postfix = { 124 services.postfix = {
125 enable = true; 125 enable = true;
126 enableSmtp = false; 126 enableSmtp = false;
127 hostname = "surtr.yggdrasil.li";
128 recipientDelimiter = "";
129 setSendmail = true; 127 setSendmail = true;
130 postmasterAlias = ""; rootAlias = ""; extraAliases = ""; 128 postmasterAlias = ""; rootAlias = ""; extraAliases = "";
131 destination = []; 129 settings.main = {
132 sslCert = "/run/credentials/postfix.service/surtr.yggdrasil.li.pem"; 130 recpipient_delimiter = "";
133 sslKey = "/run/credentials/postfix.service/surtr.yggdrasil.li.key.pem"; 131 mydestination = [];
134 networks = []; 132 mynetworks = [];
135 config = let 133 myhostname = "surtr.yggdrasil.li";
136 relay_ccert = "texthash:${pkgs.writeText "relay_ccert" ""}"; 134
137 in {
138 smtpd_tls_security_level = "may"; 135 smtpd_tls_security_level = "may";
139 136
137 smtpd_tls_chain_files = [
138 "/run/credentials/postfix.service/surtr.yggdrasil.li.full.pem"
139 ];
140
140 #the dh params 141 #the dh params
141 smtpd_tls_dh1024_param_file = toString config.security.dhparams.params."postfix-1024".path; 142 smtpd_tls_dh1024_param_file = toString config.security.dhparams.params."postfix-1024".path;
142 smtpd_tls_dh512_param_file = toString config.security.dhparams.params."postfix-512".path; 143 smtpd_tls_dh512_param_file = toString config.security.dhparams.params."postfix-512".path;
@@ -171,21 +172,14 @@ in {
171 172
172 smtp_tls_connection_reuse = true; 173 smtp_tls_connection_reuse = true;
173 174
174 tls_server_sni_maps = ''texthash:${pkgs.writeText "sni" ( 175 tls_server_sni_maps = "inline:{${concatMapStringsSep ", " (domain: "{ ${domain} = /run/credentials/postfix.service/${removePrefix "." domain}.full.pem }") (concatMap (domain: [domain "mailin.${domain}" "mailsub.${domain}" ".${domain}"]) emailDomains)}}";
175 concatMapStringsSep "\n\n" (domain:
176 concatMapStringsSep "\n" (subdomain: "${subdomain} /run/credentials/postfix.service/${removePrefix "." subdomain}.full.pem")
177 [domain "mailin.${domain}" "mailsub.${domain}" ".${domain}"]
178 ) emailDomains
179 )}'';
180 176
181 smtp_tls_policy_maps = "socketmap:unix:${config.services.postfix-mta-sts-resolver.settings.path}:postfix"; 177 smtp_tls_policy_maps = "socketmap:unix:${config.services.postfix-mta-sts-resolver.settings.path}:postfix";
182 178
183 local_recipient_maps = ""; 179 local_recipient_maps = "";
184 180
185 # 10 GiB 181 message_size_limit = 10 * 1024 * 1024 * 1024;
186 message_size_limit = "10737418240"; 182 mailbox_size_limit = 10 * 1024 * 1024 * 1024;
187 # 10 GiB
188 mailbox_size_limit = "10737418240";
189 183
190 smtpd_delay_reject = true; 184 smtpd_delay_reject = true;
191 smtpd_helo_required = true; 185 smtpd_helo_required = true;
@@ -200,7 +194,6 @@ in {
200 dbname = email 194 dbname = email
201 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' 195 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s'
202 ''}" 196 ''}"
203 "check_ccert_access ${relay_ccert}"
204 "reject_non_fqdn_helo_hostname" 197 "reject_non_fqdn_helo_hostname"
205 "reject_invalid_helo_hostname" 198 "reject_invalid_helo_hostname"
206 "reject_unauth_destination" 199 "reject_unauth_destination"
@@ -221,7 +214,6 @@ in {
221 address_verify_sender_ttl = "30045s"; 214 address_verify_sender_ttl = "30045s";
222 215
223 smtpd_relay_restrictions = [ 216 smtpd_relay_restrictions = [
224 "check_ccert_access ${relay_ccert}"
225 "reject_unauth_destination" 217 "reject_unauth_destination"
226 ]; 218 ];
227 219
@@ -244,6 +236,37 @@ in {
244 bounce_queue_lifetime = "20m"; 236 bounce_queue_lifetime = "20m";
245 delay_warning_time = "10m"; 237 delay_warning_time = "10m";
246 238
239 failure_template_file = toString (pkgs.writeText "failure.cf" ''
240 Charset: us-ascii
241 From: Mail Delivery System <MAILER-DAEMON>
242 Subject: Undelivered Mail Returned to Sender
243 Postmaster-Subject: Postmaster Copy: Undelivered Mail
244
245 This is the mail system at host $myhostname.
246
247 I'm sorry to have to inform you that your message could not
248 be delivered to one or more recipients. It's attached below.
249
250 The mail system
251 '');
252 delay_template_file = toString (pkgs.writeText "delay.cf" ''
253 Charset: us-ascii
254 From: Mail Delivery System <MAILER-DAEMON>
255 Subject: Delayed Mail (still being retried)
256 Postmaster-Subject: Postmaster Warning: Delayed Mail
257
258 This is the mail system at host $myhostname.
259
260 ####################################################################
261 # THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #
262 ####################################################################
263
264 Your message could not be delivered for more than $delay_warning_time_minutes minute(s).
265 It will be retried until it is $maximal_queue_lifetime_minutes minute(s) old.
266
267 The mail system
268 '');
269
247 smtpd_discard_ehlo_keyword_address_maps = "cidr:${pkgs.writeText "esmtp_access" '' 270 smtpd_discard_ehlo_keyword_address_maps = "cidr:${pkgs.writeText "esmtp_access" ''
248 # Allow DSN requests from local subnet only 271 # Allow DSN requests from local subnet only
249 192.168.0.0/16 silent-discard 272 192.168.0.0/16 silent-discard
@@ -268,15 +291,26 @@ in {
268 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; 291 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp";
269 smtputf8_enable = false; 292 smtputf8_enable = false;
270 293
271 authorized_submit_users = "inline:{ root= postfwd= dovecot2= }"; 294 authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.user}= }";
272 authorized_flush_users = "inline:{ root= }"; 295 authorized_flush_users = "inline:{ root= }";
273 authorized_mailq_users = "inline:{ root= }"; 296 authorized_mailq_users = "inline:{ root= }";
274 297
275 postscreen_access_list = ""; 298 postscreen_access_list = "";
276 postscreen_denylist_action = "drop"; 299 postscreen_denylist_action = "drop";
277 postscreen_greet_action = "enforce"; 300 postscreen_greet_action = "enforce";
301
302 sender_bcc_maps = ''pgsql:${pkgs.writeText "sender_bcc_maps.cf" ''
303 hosts = postgresql:///email
304 dbname = email
305 query = SELECT value FROM sender_bcc_maps WHERE key = '%s'
306 ''}'';
307 recipient_bcc_maps = ''pgsql:${pkgs.writeText "recipient_bcc_maps.cf" ''
308 hosts = postgresql:///email
309 dbname = email
310 query = SELECT value FROM recipient_bcc_maps WHERE key = '%s'
311 ''}'';
278 }; 312 };
279 masterConfig = { 313 settings.master = {
280 "465" = { 314 "465" = {
281 type = "inet"; 315 type = "inet";
282 private = false; 316 private = false;
@@ -344,7 +378,10 @@ in {
344 maxproc = 0; 378 maxproc = 0;
345 args = [ 379 args = [
346 "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" '' 380 "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" ''
381 if /^Received: /
382 !/by surtr\.yggdrasil\.li/ STRIP
347 /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 383 /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1
384 endif
348 ''}" 385 ''}"
349 ]; 386 ];
350 }; 387 };
@@ -392,7 +429,7 @@ in {
392 enable = true; 429 enable = true;
393 user = "postfix"; group = "postfix"; 430 user = "postfix"; group = "postfix";
394 socket = "local:/run/opendkim/opendkim.sock"; 431 socket = "local:/run/opendkim/opendkim.sock";
395 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li"] ++ emailDomains)}''; 432 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li" "yggdrasil.li" "141.li" "kleen.li" "synapse.li" "praseodym.org"] ++ emailDomains)}'';
396 selector = "surtr"; 433 selector = "surtr";
397 configFile = builtins.toFile "opendkim.conf" '' 434 configFile = builtins.toFile "opendkim.conf" ''
398 Syslog true 435 Syslog true
@@ -496,49 +533,49 @@ in {
496 }; 533 };
497 }; 534 };
498 535
499 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user "dovecot2" ]; 536 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user config.services.dovecot2.user ];
500 537
501 services.redis.servers.rspamd.enable = true; 538 services.redis.servers.rspamd.enable = true;
502 539
503 users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ]; 540 users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ];
504 541
505 environment.systemPackages = with pkgs; [ dovecot_pigeonhole dovecot_fts_xapian ]; 542 environment.systemPackages = with pkgs; [ dovecot_pigeonhole dovecot-fts-flatcurve ];
506 services.dovecot2 = { 543 services.dovecot2 = {
507 enable = true; 544 enable = true;
508 enablePAM = false; 545 enablePAM = false;
509 sslServerCert = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.pem"; 546 sslServerCert = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem";
510 sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; 547 sslServerKey = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem";
511 sslCACert = toString ./ca/ca.crt; 548 sslCACert = toString ./ca/ca.crt;
512 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; 549 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u";
513 mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; 550 mailPlugins.globally.enable = [ "fts" "fts_flatcurve" ];
514 protocols = [ "lmtp" "sieve" ]; 551 protocols = [ "lmtp" "sieve" ];
515 sieve = { 552 sieve = {
516 extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation"]; 553 extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"];
517 globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation"]; 554 globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"];
518 }; 555 };
519 extraConfig = let 556 extraConfig = let
520 dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' 557 dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" ''
521 driver = pgsql 558 driver = pgsql
522 connect = dbname=email 559 connect = dbname=email
523 password_query = SELECT (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN NULL ELSE "password" END) as password, (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN true WHEN password IS NULL THEN true ELSE NULL END) as nopassword, "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' 560 password_query = SELECT (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN NULL ELSE "password" END) as password, (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN true WHEN password IS NULL THEN true ELSE NULL END) as nopassword, "user", quota_rule, '${config.services.dovecot2.user}' as uid, '${config.services.dovecot2.group}' as gid FROM imap_user WHERE "user" = '%n'
524 user_query = SELECT "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' 561 user_query = SELECT "user", quota_rule, '${config.services.dovecot2.user}' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n'
525 iterate_query = SELECT "user" FROM imap_user 562 iterate_query = SELECT "user" FROM imap_user
526 ''; 563 '';
527 in '' 564 in ''
528 mail_home = /var/lib/mail/%u 565 mail_home = /var/lib/mail/%u
529 566
530 mail_plugins = $mail_plugins quota 567 mail_plugins = $mail_plugins quota fts fts_flatcurve
531 568
532 first_valid_uid = ${toString config.users.users.dovecot2.uid} 569 first_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
533 last_valid_uid = ${toString config.users.users.dovecot2.uid} 570 last_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
534 first_valid_gid = ${toString config.users.groups.dovecot2.gid} 571 first_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
535 last_valid_gid = ${toString config.users.groups.dovecot2.gid} 572 last_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
536 573
537 ${concatMapStringsSep "\n\n" (domain: 574 ${concatMapStringsSep "\n\n" (domain:
538 concatMapStringsSep "\n" (subdomain: '' 575 concatMapStringsSep "\n" (subdomain: ''
539 local_name ${subdomain} { 576 local_name ${subdomain} {
540 ssl_cert = </run/credentials/dovecot2.service/${subdomain}.pem 577 ssl_cert = </run/credentials/dovecot.service/${subdomain}.pem
541 ssl_key = </run/credentials/dovecot2.service/${subdomain}.key.pem 578 ssl_key = </run/credentials/dovecot.service/${subdomain}.key.pem
542 } 579 }
543 '') ["imap.${domain}" domain] 580 '') ["imap.${domain}" domain]
544 ) emailDomains} 581 ) emailDomains}
@@ -559,10 +596,10 @@ in {
559 auth_debug = yes 596 auth_debug = yes
560 597
561 service auth { 598 service auth {
562 user = dovecot2 599 user = ${config.services.dovecot2.user}
563 } 600 }
564 service auth-worker { 601 service auth-worker {
565 user = dovecot2 602 user = ${config.services.dovecot2.user}
566 } 603 }
567 604
568 userdb { 605 userdb {
@@ -583,7 +620,7 @@ in {
583 args = ${pkgs.writeText "dovecot-sql.conf" '' 620 args = ${pkgs.writeText "dovecot-sql.conf" ''
584 driver = pgsql 621 driver = pgsql
585 connect = dbname=email 622 connect = dbname=email
586 user_query = SELECT DISTINCT ON (extension IS NULL, local IS NULL) "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM lmtp_mapping WHERE CASE WHEN extension IS NOT NULL AND local IS NOT NULL THEN ('%n' :: citext) = local || '+' || extension AND domain = ('%d' :: citext) WHEN local IS NOT NULL THEN (local = ('%n' :: citext) OR ('%n' :: citext) ILIKE local || '+%%') AND domain = ('%d' :: citext) WHEN extension IS NOT NULL THEN ('%n' :: citext) ILIKE '%%+' || extension AND domain = ('%d' :: citext) ELSE domain = ('%d' :: citext) END ORDER BY (extension IS NULL) ASC, (local IS NULL) ASC 623 user_query = SELECT DISTINCT ON (extension IS NULL, local IS NULL) "user", quota_rule, '${config.services.dovecot2.user}' as uid, '${config.services.dovecot2.group}' as gid FROM lmtp_mapping WHERE CASE WHEN extension IS NOT NULL AND local IS NOT NULL THEN ('%n' :: citext) = local || '+' || extension AND domain = ('%d' :: citext) WHEN local IS NOT NULL THEN (local = ('%n' :: citext) OR ('%n' :: citext) ILIKE local || '+%%') AND domain = ('%d' :: citext) WHEN extension IS NOT NULL THEN ('%n' :: citext) ILIKE '%%+' || extension AND domain = ('%d' :: citext) ELSE domain = ('%d' :: citext) END ORDER BY (extension IS NULL) ASC, (local IS NULL) ASC
587 ''} 624 ''}
588 625
589 skip = never 626 skip = never
@@ -653,7 +690,7 @@ in {
653 quota_status_success = DUNNO 690 quota_status_success = DUNNO
654 quota_status_nouser = DUNNO 691 quota_status_nouser = DUNNO
655 quota_grace = 10%% 692 quota_grace = 10%%
656 quota_max_mail_size = ${config.services.postfix.config.message_size_limit} 693 quota_max_mail_size = ${toString config.services.postfix.settings.main.message_size_limit}
657 quota_vsizes = yes 694 quota_vsizes = yes
658 } 695 }
659 696
@@ -689,13 +726,16 @@ in {
689 } 726 }
690 727
691 plugin { 728 plugin {
692 plugin = fts fts_xapian 729 fts = flatcurve
693 fts = xapian
694 fts_xapian = partial=3 full=20 attachments=1 verbose=1
695 730
696 fts_autoindex = yes 731 fts_languages = en de
732 fts_tokenizers = generic email-address
697 733
698 fts_enforced = no 734 fts_tokenizer_email_address = maxlen=100
735 fts_tokenizer_generic = algorithm=simple maxlen=30
736
737 fts_filters = normalizer-icu snowball stopwords
738 fts_filters_en = lowercase snowball stopwords
699 } 739 }
700 740
701 service indexer-worker { 741 service indexer-worker {
@@ -704,30 +744,6 @@ in {
704 ''; 744 '';
705 }; 745 };
706 746
707 systemd.services.dovecot-fts-xapian-optimize = {
708 description = "Optimize dovecot indices for fts_xapian";
709 requisite = [ "dovecot2.service" ];
710 after = [ "dovecot2.service" ];
711 startAt = "*-*-* 22:00:00 Europe/Berlin";
712 serviceConfig = {
713 Type = "oneshot";
714 ExecStart = "${getExe' pkgs.dovecot "doveadm"} fts optimize -A";
715 PrivateDevices = true;
716 PrivateNetwork = true;
717 ProtectKernelTunables = true;
718 ProtectKernelModules = true;
719 ProtectControlGroups = true;
720 ProtectHome = true;
721 ProtectSystem = true;
722 PrivateTmp = true;
723 };
724 };
725 systemd.timers.dovecot-fts-xapian-optimize = {
726 timerConfig = {
727 RandomizedDelaySec = 4 * 3600;
728 };
729 };
730
731 environment.etc = { 747 environment.etc = {
732 "dovecot/sieve_before.d/tag-junk.sieve".text = '' 748 "dovecot/sieve_before.d/tag-junk.sieve".text = ''
733 require ["imap4flags"]; 749 require ["imap4flags"];
@@ -772,28 +788,26 @@ in {
772 788
773 security.acme.rfc2136Domains = { 789 security.acme.rfc2136Domains = {
774 "surtr.yggdrasil.li" = { 790 "surtr.yggdrasil.li" = {
775 restartUnits = [ "postfix.service" "dovecot2.service" ]; 791 restartUnits = [ "postfix.service" "dovecot.service" ];
776 }; 792 };
777 } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains) 793 } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains)
778 // listToAttrs (concatMap (domain: [ 794 // listToAttrs (concatMap (domain: [
779 (nameValuePair domain { restartUnits = ["postfix.service" "dovecot2.service"]; }) 795 (nameValuePair domain { restartUnits = ["postfix.service" "dovecot.service"]; })
780 (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; }) 796 (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; })
781 (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; }) 797 (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; })
782 (nameValuePair "imap.${domain}" { restartUnits = ["dovecot2.service"]; }) 798 (nameValuePair "imap.${domain}" { restartUnits = ["dovecot.service"]; })
783 (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; }) 799 (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; })
784 ]) emailDomains); 800 ]) emailDomains);
785 801
786 systemd.services.postfix = { 802 systemd.services.postfix = {
787 serviceConfig.LoadCredential = [ 803 serviceConfig.LoadCredential = let
788 "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem" 804 tlsCredential = domain: "${domain}.full.pem:${config.security.acme.certs.${domain}.directory}/full.pem";
789 "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem" 805 in [
790 ] ++ concatMap (domain: 806 (tlsCredential "surtr.yggdrasil.li")
791 map (subdomain: "${subdomain}.full.pem:${config.security.acme.certs.${subdomain}.directory}/full.pem") 807 ] ++ concatMap (domain: map tlsCredential [domain "mailin.${domain}" "mailsub.${domain}"]) emailDomains;
792 [domain "mailin.${domain}" "mailsub.${domain}"]
793 ) emailDomains;
794 }; 808 };
795 809
796 systemd.services.dovecot2 = { 810 systemd.services.dovecot = {
797 preStart = '' 811 preStart = ''
798 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do 812 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do
799 ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f 813 ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f