diff options
Diffstat (limited to 'hosts/surtr/email/default.nix')
-rw-r--r-- | hosts/surtr/email/default.nix | 217 |
1 files changed, 144 insertions, 73 deletions
diff --git a/hosts/surtr/email/default.nix b/hosts/surtr/email/default.nix index 845f6455..a3e06ca6 100644 --- a/hosts/surtr/email/default.nix +++ b/hosts/surtr/email/default.nix | |||
@@ -1,4 +1,4 @@ | |||
1 | { config, pkgs, lib, flakeInputs, ... }: | 1 | { config, pkgs, lib, flake, flakeInputs, ... }: |
2 | 2 | ||
3 | with lib; | 3 | with lib; |
4 | 4 | ||
@@ -15,7 +15,7 @@ let | |||
15 | 15 | ||
16 | for file in $out/pipe/bin/*; do | 16 | for file in $out/pipe/bin/*; do |
17 | wrapProgram $file \ | 17 | wrapProgram $file \ |
18 | --set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin" | 18 | --set PATH "${makeBinPath (with pkgs; [coreutils rspamd])}" |
19 | done | 19 | done |
20 | ''; | 20 | ''; |
21 | }; | 21 | }; |
@@ -33,12 +33,28 @@ let | |||
33 | }); | 33 | }); |
34 | }); | 34 | }); |
35 | }; | 35 | }; |
36 | internal-policy-server = | ||
37 | let | ||
38 | workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./internal-policy-server; }; | ||
39 | pythonSet = flake.lib.pythonSet { | ||
40 | inherit pkgs; | ||
41 | python = pkgs.python312; | ||
42 | overlay = workspace.mkPyprojectOverlay { | ||
43 | sourcePreference = "wheel"; | ||
44 | }; | ||
45 | }; | ||
46 | virtualEnv = pythonSet.mkVirtualEnv "internal-policy-server-env" workspace.deps.default; | ||
47 | in virtualEnv.overrideAttrs (oldAttrs: { | ||
48 | meta = (oldAttrs.meta or {}) // { | ||
49 | mainProgram = "internal-policy-server"; | ||
50 | }; | ||
51 | }); | ||
36 | 52 | ||
37 | nftables-nologin-script = pkgs.writeScript "nftables-mail-nologin" '' | 53 | nftables-nologin-script = pkgs.resholve.writeScript "nftables-mail-nologin" { |
38 | #!${pkgs.zsh}/bin/zsh | 54 | inputs = with pkgs; [inetutils nftables gnugrep findutils]; |
39 | 55 | interpreter = lib.getExe pkgs.zsh; | |
56 | } '' | ||
40 | set -e | 57 | set -e |
41 | export PATH="${lib.makeBinPath (with pkgs; [inetutils nftables])}:$PATH" | ||
42 | 58 | ||
43 | typeset -a as_sets mnt_bys route route6 | 59 | typeset -a as_sets mnt_bys route route6 |
44 | as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets}) | 60 | as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets}) |
@@ -51,7 +67,7 @@ let | |||
51 | elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then | 67 | elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then |
52 | route6+=($match[1]) | 68 | route6+=($match[1]) |
53 | fi | 69 | fi |
54 | done < <(whois -h whois.radb.net "!i''${as_set},1" | egrep -o 'AS[0-9]+' | xargs -- whois -h whois.radb.net -- -i origin) | 70 | done < <(whois -h whois.radb.net "!i''${as_set},1" | grep -Eo 'AS[0-9]+' | xargs whois -h whois.radb.net -- -i origin) |
55 | done | 71 | done |
56 | for mnt_by in $mnt_bys; do | 72 | for mnt_by in $mnt_bys; do |
57 | while IFS=$'\n' read line; do | 73 | while IFS=$'\n' read line; do |
@@ -108,19 +124,20 @@ in { | |||
108 | services.postfix = { | 124 | services.postfix = { |
109 | enable = true; | 125 | enable = true; |
110 | enableSmtp = false; | 126 | enableSmtp = false; |
111 | hostname = "surtr.yggdrasil.li"; | ||
112 | recipientDelimiter = ""; | ||
113 | setSendmail = true; | 127 | setSendmail = true; |
114 | postmasterAlias = ""; rootAlias = ""; extraAliases = ""; | 128 | postmasterAlias = ""; rootAlias = ""; extraAliases = ""; |
115 | destination = []; | 129 | settings.main = { |
116 | sslCert = "/run/credentials/postfix.service/surtr.yggdrasil.li.pem"; | 130 | recpipient_delimiter = ""; |
117 | sslKey = "/run/credentials/postfix.service/surtr.yggdrasil.li.key.pem"; | 131 | mydestination = []; |
118 | networks = []; | 132 | mynetworks = []; |
119 | config = let | 133 | myhostname = "surtr.yggdrasil.li"; |
120 | relay_ccert = "texthash:${pkgs.writeText "relay_ccert" ""}"; | 134 | |
121 | in { | ||
122 | smtpd_tls_security_level = "may"; | 135 | smtpd_tls_security_level = "may"; |
123 | 136 | ||
137 | smtpd_tls_chain_files = [ | ||
138 | "/run/credentials/postfix.service/surtr.yggdrasil.li.full.pem" | ||
139 | ]; | ||
140 | |||
124 | #the dh params | 141 | #the dh params |
125 | 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; |
126 | 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; |
@@ -155,21 +172,14 @@ in { | |||
155 | 172 | ||
156 | smtp_tls_connection_reuse = true; | 173 | smtp_tls_connection_reuse = true; |
157 | 174 | ||
158 | 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)}}"; |
159 | concatMapStringsSep "\n\n" (domain: | ||
160 | concatMapStringsSep "\n" (subdomain: "${subdomain} /run/credentials/postfix.service/${removePrefix "." subdomain}.full.pem") | ||
161 | [domain "mailin.${domain}" "mailsub.${domain}" ".${domain}"] | ||
162 | ) emailDomains | ||
163 | )}''; | ||
164 | 176 | ||
165 | 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"; |
166 | 178 | ||
167 | local_recipient_maps = ""; | 179 | local_recipient_maps = ""; |
168 | 180 | ||
169 | # 10 GiB | 181 | message_size_limit = 10 * 1024 * 1024 * 1024; |
170 | message_size_limit = "10737418240"; | 182 | mailbox_size_limit = 10 * 1024 * 1024 * 1024; |
171 | # 10 GiB | ||
172 | mailbox_size_limit = "10737418240"; | ||
173 | 183 | ||
174 | smtpd_delay_reject = true; | 184 | smtpd_delay_reject = true; |
175 | smtpd_helo_required = true; | 185 | smtpd_helo_required = true; |
@@ -184,12 +194,12 @@ in { | |||
184 | dbname = email | 194 | dbname = email |
185 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' | 195 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' |
186 | ''}" | 196 | ''}" |
187 | "check_ccert_access ${relay_ccert}" | ||
188 | "reject_non_fqdn_helo_hostname" | 197 | "reject_non_fqdn_helo_hostname" |
189 | "reject_invalid_helo_hostname" | 198 | "reject_invalid_helo_hostname" |
190 | "reject_unauth_destination" | 199 | "reject_unauth_destination" |
191 | "reject_unknown_recipient_domain" | 200 | "reject_unknown_recipient_domain" |
192 | "reject_unverified_recipient" | 201 | "reject_unverified_recipient" |
202 | "check_policy_service unix:/run/postfix-internal-policy.sock" | ||
193 | ]; | 203 | ]; |
194 | unverified_recipient_reject_code = "550"; | 204 | unverified_recipient_reject_code = "550"; |
195 | unverified_recipient_reject_reason = "Recipient address lookup failed"; | 205 | unverified_recipient_reject_reason = "Recipient address lookup failed"; |
@@ -204,7 +214,6 @@ in { | |||
204 | address_verify_sender_ttl = "30045s"; | 214 | address_verify_sender_ttl = "30045s"; |
205 | 215 | ||
206 | smtpd_relay_restrictions = [ | 216 | smtpd_relay_restrictions = [ |
207 | "check_ccert_access ${relay_ccert}" | ||
208 | "reject_unauth_destination" | 217 | "reject_unauth_destination" |
209 | ]; | 218 | ]; |
210 | 219 | ||
@@ -251,13 +260,26 @@ in { | |||
251 | virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; | 260 | virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; |
252 | smtputf8_enable = false; | 261 | smtputf8_enable = false; |
253 | 262 | ||
254 | authorized_submit_users = "inline:{ root= postfwd= }"; | 263 | authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.user}= }"; |
264 | authorized_flush_users = "inline:{ root= }"; | ||
265 | authorized_mailq_users = "inline:{ root= }"; | ||
255 | 266 | ||
256 | postscreen_access_list = ""; | 267 | postscreen_access_list = ""; |
257 | postscreen_denylist_action = "drop"; | 268 | postscreen_denylist_action = "drop"; |
258 | postscreen_greet_action = "enforce"; | 269 | postscreen_greet_action = "enforce"; |
270 | |||
271 | sender_bcc_maps = ''pgsql:${pkgs.writeText "sender_bcc_maps.cf" '' | ||
272 | hosts = postgresql:///email | ||
273 | dbname = email | ||
274 | query = SELECT value FROM sender_bcc_maps WHERE key = '%s' | ||
275 | ''}''; | ||
276 | recipient_bcc_maps = ''pgsql:${pkgs.writeText "recipient_bcc_maps.cf" '' | ||
277 | hosts = postgresql:///email | ||
278 | dbname = email | ||
279 | query = SELECT value FROM recipient_bcc_maps WHERE key = '%s' | ||
280 | ''}''; | ||
259 | }; | 281 | }; |
260 | masterConfig = { | 282 | settings.master = { |
261 | "465" = { | 283 | "465" = { |
262 | type = "inet"; | 284 | type = "inet"; |
263 | private = false; | 285 | private = false; |
@@ -280,7 +302,7 @@ in { | |||
280 | hosts = postgresql:///email | 302 | hosts = postgresql:///email |
281 | dbname = email | 303 | dbname = email |
282 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) | 304 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) |
283 | ''},permit_tls_all_clientcerts,reject}'' | 305 | ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_tls_all_clientcerts,reject}'' |
284 | "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" | 306 | "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" |
285 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" | 307 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" |
286 | "-o" "unverified_sender_reject_code=550" | 308 | "-o" "unverified_sender_reject_code=550" |
@@ -310,7 +332,7 @@ in { | |||
310 | hosts = postgresql:///email | 332 | hosts = postgresql:///email |
311 | dbname = email | 333 | dbname = email |
312 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) | 334 | query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) |
313 | ''},permit_sasl_authenticated,reject}'' | 335 | ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_sasl_authenticated,reject}'' |
314 | "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" | 336 | "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" |
315 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" | 337 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" |
316 | "-o" "unverified_sender_reject_code=550" | 338 | "-o" "unverified_sender_reject_code=550" |
@@ -325,7 +347,10 @@ in { | |||
325 | maxproc = 0; | 347 | maxproc = 0; |
326 | args = [ | 348 | args = [ |
327 | "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" '' | 349 | "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" '' |
350 | if /^Received: / | ||
351 | !/by surtr\.yggdrasil\.li/ STRIP | ||
328 | /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 | 352 | /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 |
353 | endif | ||
329 | ''}" | 354 | ''}" |
330 | ]; | 355 | ]; |
331 | }; | 356 | }; |
@@ -373,7 +398,7 @@ in { | |||
373 | enable = true; | 398 | enable = true; |
374 | user = "postfix"; group = "postfix"; | 399 | user = "postfix"; group = "postfix"; |
375 | socket = "local:/run/opendkim/opendkim.sock"; | 400 | socket = "local:/run/opendkim/opendkim.sock"; |
376 | domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li"] ++ emailDomains)}''; | 401 | domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li" "yggdrasil.li" "141.li" "kleen.li" "synapse.li" "praseodym.org"] ++ emailDomains)}''; |
377 | selector = "surtr"; | 402 | selector = "surtr"; |
378 | configFile = builtins.toFile "opendkim.conf" '' | 403 | configFile = builtins.toFile "opendkim.conf" '' |
379 | Syslog true | 404 | Syslog true |
@@ -477,7 +502,7 @@ in { | |||
477 | }; | 502 | }; |
478 | }; | 503 | }; |
479 | 504 | ||
480 | users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user "dovecot2" ]; | 505 | users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user config.services.dovecot2.user ]; |
481 | 506 | ||
482 | services.redis.servers.rspamd.enable = true; | 507 | services.redis.servers.rspamd.enable = true; |
483 | 508 | ||
@@ -487,22 +512,22 @@ in { | |||
487 | services.dovecot2 = { | 512 | services.dovecot2 = { |
488 | enable = true; | 513 | enable = true; |
489 | enablePAM = false; | 514 | enablePAM = false; |
490 | sslServerCert = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.pem"; | 515 | sslServerCert = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem"; |
491 | sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; | 516 | sslServerKey = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem"; |
492 | sslCACert = toString ./ca/ca.crt; | 517 | sslCACert = toString ./ca/ca.crt; |
493 | mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; | 518 | mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; |
494 | mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; | 519 | mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; |
495 | protocols = [ "lmtp" "sieve" ]; | 520 | protocols = [ "lmtp" "sieve" ]; |
496 | sieve = { | 521 | sieve = { |
497 | extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation"]; | 522 | extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"]; |
498 | globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation"]; | 523 | globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"]; |
499 | }; | 524 | }; |
500 | extraConfig = let | 525 | extraConfig = let |
501 | dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' | 526 | dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' |
502 | driver = pgsql | 527 | driver = pgsql |
503 | connect = dbname=email | 528 | connect = dbname=email |
504 | 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' | 529 | 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' |
505 | user_query = SELECT "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' | 530 | user_query = SELECT "user", quota_rule, '${config.services.dovecot2.user}' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' |
506 | iterate_query = SELECT "user" FROM imap_user | 531 | iterate_query = SELECT "user" FROM imap_user |
507 | ''; | 532 | ''; |
508 | in '' | 533 | in '' |
@@ -510,16 +535,16 @@ in { | |||
510 | 535 | ||
511 | mail_plugins = $mail_plugins quota | 536 | mail_plugins = $mail_plugins quota |
512 | 537 | ||
513 | first_valid_uid = ${toString config.users.users.dovecot2.uid} | 538 | first_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid} |
514 | last_valid_uid = ${toString config.users.users.dovecot2.uid} | 539 | last_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid} |
515 | first_valid_gid = ${toString config.users.groups.dovecot2.gid} | 540 | first_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid} |
516 | last_valid_gid = ${toString config.users.groups.dovecot2.gid} | 541 | last_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid} |
517 | 542 | ||
518 | ${concatMapStringsSep "\n\n" (domain: | 543 | ${concatMapStringsSep "\n\n" (domain: |
519 | concatMapStringsSep "\n" (subdomain: '' | 544 | concatMapStringsSep "\n" (subdomain: '' |
520 | local_name ${subdomain} { | 545 | local_name ${subdomain} { |
521 | ssl_cert = </run/credentials/dovecot2.service/${subdomain}.pem | 546 | ssl_cert = </run/credentials/dovecot.service/${subdomain}.pem |
522 | ssl_key = </run/credentials/dovecot2.service/${subdomain}.key.pem | 547 | ssl_key = </run/credentials/dovecot.service/${subdomain}.key.pem |
523 | } | 548 | } |
524 | '') ["imap.${domain}" domain] | 549 | '') ["imap.${domain}" domain] |
525 | ) emailDomains} | 550 | ) emailDomains} |
@@ -540,10 +565,10 @@ in { | |||
540 | auth_debug = yes | 565 | auth_debug = yes |
541 | 566 | ||
542 | service auth { | 567 | service auth { |
543 | user = dovecot2 | 568 | user = ${config.services.dovecot2.user} |
544 | } | 569 | } |
545 | service auth-worker { | 570 | service auth-worker { |
546 | user = dovecot2 | 571 | user = ${config.services.dovecot2.user} |
547 | } | 572 | } |
548 | 573 | ||
549 | userdb { | 574 | userdb { |
@@ -564,7 +589,7 @@ in { | |||
564 | args = ${pkgs.writeText "dovecot-sql.conf" '' | 589 | args = ${pkgs.writeText "dovecot-sql.conf" '' |
565 | driver = pgsql | 590 | driver = pgsql |
566 | connect = dbname=email | 591 | connect = dbname=email |
567 | 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 | 592 | 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 |
568 | ''} | 593 | ''} |
569 | 594 | ||
570 | skip = never | 595 | skip = never |
@@ -634,7 +659,7 @@ in { | |||
634 | quota_status_success = DUNNO | 659 | quota_status_success = DUNNO |
635 | quota_status_nouser = DUNNO | 660 | quota_status_nouser = DUNNO |
636 | quota_grace = 10%% | 661 | quota_grace = 10%% |
637 | quota_max_mail_size = ${config.services.postfix.config.message_size_limit} | 662 | quota_max_mail_size = ${toString config.services.postfix.settings.main.message_size_limit} |
638 | quota_vsizes = yes | 663 | quota_vsizes = yes |
639 | } | 664 | } |
640 | 665 | ||
@@ -672,7 +697,7 @@ in { | |||
672 | plugin { | 697 | plugin { |
673 | plugin = fts fts_xapian | 698 | plugin = fts fts_xapian |
674 | fts = xapian | 699 | fts = xapian |
675 | fts_xapian = partial=2 full=20 attachments=1 verbose=1 | 700 | fts_xapian = partial=3 full=20 attachments=1 verbose=1 |
676 | 701 | ||
677 | fts_autoindex = yes | 702 | fts_autoindex = yes |
678 | 703 | ||
@@ -687,12 +712,12 @@ in { | |||
687 | 712 | ||
688 | systemd.services.dovecot-fts-xapian-optimize = { | 713 | systemd.services.dovecot-fts-xapian-optimize = { |
689 | description = "Optimize dovecot indices for fts_xapian"; | 714 | description = "Optimize dovecot indices for fts_xapian"; |
690 | requisite = [ "dovecot2.service" ]; | 715 | requisite = [ "dovecot.service" ]; |
691 | after = [ "dovecot2.service" ]; | 716 | after = [ "dovecot.service" ]; |
692 | startAt = "*-*-* 22:00:00 Europe/Berlin"; | 717 | startAt = "*-*-* 22:00:00 Europe/Berlin"; |
693 | serviceConfig = { | 718 | serviceConfig = { |
694 | Type = "oneshot"; | 719 | Type = "oneshot"; |
695 | ExecStart = "${pkgs.dovecot}/bin/doveadm fts optimize -A"; | 720 | ExecStart = "${getExe' pkgs.dovecot "doveadm"} fts optimize -A"; |
696 | PrivateDevices = true; | 721 | PrivateDevices = true; |
697 | PrivateNetwork = true; | 722 | PrivateNetwork = true; |
698 | ProtectKernelTunables = true; | 723 | ProtectKernelTunables = true; |
@@ -753,31 +778,29 @@ in { | |||
753 | 778 | ||
754 | security.acme.rfc2136Domains = { | 779 | security.acme.rfc2136Domains = { |
755 | "surtr.yggdrasil.li" = { | 780 | "surtr.yggdrasil.li" = { |
756 | restartUnits = [ "postfix.service" "dovecot2.service" ]; | 781 | restartUnits = [ "postfix.service" "dovecot.service" ]; |
757 | }; | 782 | }; |
758 | } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains) | 783 | } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains) |
759 | // listToAttrs (concatMap (domain: [ | 784 | // listToAttrs (concatMap (domain: [ |
760 | (nameValuePair domain { restartUnits = ["postfix.service" "dovecot2.service"]; }) | 785 | (nameValuePair domain { restartUnits = ["postfix.service" "dovecot.service"]; }) |
761 | (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; }) | 786 | (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; }) |
762 | (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; }) | 787 | (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; }) |
763 | (nameValuePair "imap.${domain}" { restartUnits = ["dovecot2.service"]; }) | 788 | (nameValuePair "imap.${domain}" { restartUnits = ["dovecot.service"]; }) |
764 | (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; }) | 789 | (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; }) |
765 | ]) emailDomains); | 790 | ]) emailDomains); |
766 | 791 | ||
767 | systemd.services.postfix = { | 792 | systemd.services.postfix = { |
768 | serviceConfig.LoadCredential = [ | 793 | serviceConfig.LoadCredential = let |
769 | "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem" | 794 | tlsCredential = domain: "${domain}.full.pem:${config.security.acme.certs.${domain}.directory}/full.pem"; |
770 | "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem" | 795 | in [ |
771 | ] ++ concatMap (domain: | 796 | (tlsCredential "surtr.yggdrasil.li") |
772 | map (subdomain: "${subdomain}.full.pem:${config.security.acme.certs.${subdomain}.directory}/full.pem") | 797 | ] ++ concatMap (domain: map tlsCredential [domain "mailin.${domain}" "mailsub.${domain}"]) emailDomains; |
773 | [domain "mailin.${domain}" "mailsub.${domain}"] | ||
774 | ) emailDomains; | ||
775 | }; | 798 | }; |
776 | 799 | ||
777 | systemd.services.dovecot2 = { | 800 | systemd.services.dovecot = { |
778 | preStart = '' | 801 | preStart = '' |
779 | for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do | 802 | for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do |
780 | ${pkgs.dovecot_pigeonhole}/bin/sievec $f | 803 | ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f |
781 | done | 804 | done |
782 | ''; | 805 | ''; |
783 | 806 | ||
@@ -844,15 +867,16 @@ in { | |||
844 | charset utf-8; | 867 | charset utf-8; |
845 | source_charset utf-8; | 868 | source_charset utf-8; |
846 | ''; | 869 | ''; |
847 | root = pkgs.runCommand "mta-sts.${domain}" {} '' | 870 | root = pkgs.writeTextFile { |
848 | mkdir -p $out/.well-known | 871 | name = "mta-sts.${domain}"; |
849 | cp ${pkgs.writeText "mta-sts.${domain}.txt" '' | 872 | destination = "/.well-known/mta-sts.txt"; |
873 | text = '' | ||
850 | version: STSv1 | 874 | version: STSv1 |
851 | mode: enforce | 875 | mode: enforce |
852 | max_age: 2419200 | 876 | max_age: 2419200 |
853 | mx: mailin.${domain} | 877 | mx: mailin.${domain} |
854 | ''} $out/.well-known/mta-sts.txt | 878 | ''; |
855 | ''; | 879 | }; |
856 | }; | 880 | }; |
857 | }) emailDomains); | 881 | }) emailDomains); |
858 | }; | 882 | }; |
@@ -869,7 +893,7 @@ in { | |||
869 | systemd.services.spm = { | 893 | systemd.services.spm = { |
870 | serviceConfig = { | 894 | serviceConfig = { |
871 | Type = "notify"; | 895 | Type = "notify"; |
872 | ExecStart = "${pkgs.spm}/bin/spm-server"; | 896 | ExecStart = getExe' pkgs.spm "spm-server"; |
873 | User = "spm"; | 897 | User = "spm"; |
874 | Group = "spm"; | 898 | Group = "spm"; |
875 | 899 | ||
@@ -927,7 +951,7 @@ in { | |||
927 | serviceConfig = { | 951 | serviceConfig = { |
928 | Type = "notify"; | 952 | Type = "notify"; |
929 | 953 | ||
930 | ExecStart = "${ccert-policy-server}/bin/ccert-policy-server"; | 954 | ExecStart = getExe' ccert-policy-server "ccert-policy-server"; |
931 | 955 | ||
932 | Environment = [ | 956 | Environment = [ |
933 | "PGDATABASE=email" | 957 | "PGDATABASE=email" |
@@ -960,6 +984,53 @@ in { | |||
960 | }; | 984 | }; |
961 | users.groups."postfix-ccert-sender-policy" = {}; | 985 | users.groups."postfix-ccert-sender-policy" = {}; |
962 | 986 | ||
987 | systemd.sockets."postfix-internal-policy" = { | ||
988 | requiredBy = ["postfix.service"]; | ||
989 | wants = ["postfix-internal-policy.service"]; | ||
990 | socketConfig = { | ||
991 | ListenStream = "/run/postfix-internal-policy.sock"; | ||
992 | }; | ||
993 | }; | ||
994 | systemd.services."postfix-internal-policy" = { | ||
995 | after = [ "postgresql.service" ]; | ||
996 | bindsTo = [ "postgresql.service" ]; | ||
997 | |||
998 | serviceConfig = { | ||
999 | Type = "notify"; | ||
1000 | |||
1001 | ExecStart = lib.getExe internal-policy-server; | ||
1002 | |||
1003 | Environment = [ | ||
1004 | "PGDATABASE=email" | ||
1005 | ]; | ||
1006 | |||
1007 | DynamicUser = false; | ||
1008 | User = "postfix-internal-policy"; | ||
1009 | Group = "postfix-internal-policy"; | ||
1010 | ProtectSystem = "strict"; | ||
1011 | SystemCallFilter = "@system-service"; | ||
1012 | NoNewPrivileges = true; | ||
1013 | ProtectKernelTunables = true; | ||
1014 | ProtectKernelModules = true; | ||
1015 | ProtectKernelLogs = true; | ||
1016 | ProtectControlGroups = true; | ||
1017 | MemoryDenyWriteExecute = true; | ||
1018 | RestrictSUIDSGID = true; | ||
1019 | KeyringMode = "private"; | ||
1020 | ProtectClock = true; | ||
1021 | RestrictRealtime = true; | ||
1022 | PrivateDevices = true; | ||
1023 | PrivateTmp = true; | ||
1024 | ProtectHostname = true; | ||
1025 | ReadWritePaths = ["/run/postgresql"]; | ||
1026 | }; | ||
1027 | }; | ||
1028 | users.users."postfix-internal-policy" = { | ||
1029 | isSystemUser = true; | ||
1030 | group = "postfix-internal-policy"; | ||
1031 | }; | ||
1032 | users.groups."postfix-internal-policy" = {}; | ||
1033 | |||
963 | services.postfwd = { | 1034 | services.postfwd = { |
964 | enable = true; | 1035 | enable = true; |
965 | cache = false; | 1036 | cache = false; |