diff options
Diffstat (limited to 'hosts/surtr/email/default.nix')
| -rw-r--r-- | hosts/surtr/email/default.nix | 283 |
1 files changed, 182 insertions, 101 deletions
diff --git a/hosts/surtr/email/default.nix b/hosts/surtr/email/default.nix index 845f6455..b0e95a0e 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 | ||
| @@ -227,6 +236,37 @@ in { | |||
| 227 | bounce_queue_lifetime = "20m"; | 236 | bounce_queue_lifetime = "20m"; |
| 228 | delay_warning_time = "10m"; | 237 | delay_warning_time = "10m"; |
| 229 | 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 | |||
| 230 | smtpd_discard_ehlo_keyword_address_maps = "cidr:${pkgs.writeText "esmtp_access" '' | 270 | smtpd_discard_ehlo_keyword_address_maps = "cidr:${pkgs.writeText "esmtp_access" '' |
| 231 | # Allow DSN requests from local subnet only | 271 | # Allow DSN requests from local subnet only |
| 232 | 192.168.0.0/16 silent-discard | 272 | 192.168.0.0/16 silent-discard |
| @@ -251,13 +291,26 @@ in { | |||
| 251 | virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; | 291 | virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; |
| 252 | smtputf8_enable = false; | 292 | smtputf8_enable = false; |
| 253 | 293 | ||
| 254 | authorized_submit_users = "inline:{ root= postfwd= }"; | 294 | authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.user}= }"; |
| 295 | authorized_flush_users = "inline:{ root= }"; | ||
| 296 | authorized_mailq_users = "inline:{ root= }"; | ||
| 255 | 297 | ||
| 256 | postscreen_access_list = ""; | 298 | postscreen_access_list = ""; |
| 257 | postscreen_denylist_action = "drop"; | 299 | postscreen_denylist_action = "drop"; |
| 258 | 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 | ''}''; | ||
| 259 | }; | 312 | }; |
| 260 | masterConfig = { | 313 | settings.master = { |
| 261 | "465" = { | 314 | "465" = { |
| 262 | type = "inet"; | 315 | type = "inet"; |
| 263 | private = false; | 316 | private = false; |
| @@ -280,7 +333,7 @@ in { | |||
| 280 | hosts = postgresql:///email | 333 | hosts = postgresql:///email |
| 281 | dbname = email | 334 | 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')) | 335 | 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}'' | 336 | ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_tls_all_clientcerts,reject}'' |
| 284 | "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" | 337 | "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" |
| 285 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" | 338 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" |
| 286 | "-o" "unverified_sender_reject_code=550" | 339 | "-o" "unverified_sender_reject_code=550" |
| @@ -310,7 +363,7 @@ in { | |||
| 310 | hosts = postgresql:///email | 363 | hosts = postgresql:///email |
| 311 | dbname = email | 364 | 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')) | 365 | 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}'' | 366 | ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_sasl_authenticated,reject}'' |
| 314 | "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" | 367 | "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" |
| 315 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" | 368 | "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" |
| 316 | "-o" "unverified_sender_reject_code=550" | 369 | "-o" "unverified_sender_reject_code=550" |
| @@ -325,7 +378,10 @@ in { | |||
| 325 | maxproc = 0; | 378 | maxproc = 0; |
| 326 | args = [ | 379 | args = [ |
| 327 | "-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 | ||
| 328 | /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 | 383 | /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 |
| 384 | endif | ||
| 329 | ''}" | 385 | ''}" |
| 330 | ]; | 386 | ]; |
| 331 | }; | 387 | }; |
| @@ -373,7 +429,7 @@ in { | |||
| 373 | enable = true; | 429 | enable = true; |
| 374 | user = "postfix"; group = "postfix"; | 430 | user = "postfix"; group = "postfix"; |
| 375 | socket = "local:/run/opendkim/opendkim.sock"; | 431 | socket = "local:/run/opendkim/opendkim.sock"; |
| 376 | 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)}''; |
| 377 | selector = "surtr"; | 433 | selector = "surtr"; |
| 378 | configFile = builtins.toFile "opendkim.conf" '' | 434 | configFile = builtins.toFile "opendkim.conf" '' |
| 379 | Syslog true | 435 | Syslog true |
| @@ -477,49 +533,49 @@ in { | |||
| 477 | }; | 533 | }; |
| 478 | }; | 534 | }; |
| 479 | 535 | ||
| 480 | 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 ]; |
| 481 | 537 | ||
| 482 | services.redis.servers.rspamd.enable = true; | 538 | services.redis.servers.rspamd.enable = true; |
| 483 | 539 | ||
| 484 | 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 ]; |
| 485 | 541 | ||
| 486 | environment.systemPackages = with pkgs; [ dovecot_pigeonhole dovecot_fts_xapian ]; | 542 | environment.systemPackages = with pkgs; [ dovecot_pigeonhole dovecot-fts-flatcurve ]; |
| 487 | services.dovecot2 = { | 543 | services.dovecot2 = { |
| 488 | enable = true; | 544 | enable = true; |
| 489 | enablePAM = false; | 545 | enablePAM = false; |
| 490 | sslServerCert = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.pem"; | 546 | sslServerCert = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem"; |
| 491 | sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; | 547 | sslServerKey = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem"; |
| 492 | sslCACert = toString ./ca/ca.crt; | 548 | sslCACert = toString ./ca/ca.crt; |
| 493 | 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"; |
| 494 | mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; | 550 | mailPlugins.globally.enable = [ "fts" "fts_flatcurve" ]; |
| 495 | protocols = [ "lmtp" "sieve" ]; | 551 | protocols = [ "lmtp" "sieve" ]; |
| 496 | sieve = { | 552 | sieve = { |
| 497 | extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation"]; | 553 | extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"]; |
| 498 | globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation"]; | 554 | globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"]; |
| 499 | }; | 555 | }; |
| 500 | extraConfig = let | 556 | extraConfig = let |
| 501 | dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' | 557 | dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' |
| 502 | driver = pgsql | 558 | driver = pgsql |
| 503 | connect = dbname=email | 559 | 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' | 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' |
| 505 | 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' |
| 506 | iterate_query = SELECT "user" FROM imap_user | 562 | iterate_query = SELECT "user" FROM imap_user |
| 507 | ''; | 563 | ''; |
| 508 | in '' | 564 | in '' |
| 509 | mail_home = /var/lib/mail/%u | 565 | mail_home = /var/lib/mail/%u |
| 510 | 566 | ||
| 511 | mail_plugins = $mail_plugins quota | 567 | mail_plugins = $mail_plugins quota fts fts_flatcurve |
| 512 | 568 | ||
| 513 | first_valid_uid = ${toString config.users.users.dovecot2.uid} | 569 | first_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid} |
| 514 | last_valid_uid = ${toString config.users.users.dovecot2.uid} | 570 | last_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid} |
| 515 | first_valid_gid = ${toString config.users.groups.dovecot2.gid} | 571 | first_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid} |
| 516 | last_valid_gid = ${toString config.users.groups.dovecot2.gid} | 572 | last_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid} |
| 517 | 573 | ||
| 518 | ${concatMapStringsSep "\n\n" (domain: | 574 | ${concatMapStringsSep "\n\n" (domain: |
| 519 | concatMapStringsSep "\n" (subdomain: '' | 575 | concatMapStringsSep "\n" (subdomain: '' |
| 520 | local_name ${subdomain} { | 576 | local_name ${subdomain} { |
| 521 | ssl_cert = </run/credentials/dovecot2.service/${subdomain}.pem | 577 | ssl_cert = </run/credentials/dovecot.service/${subdomain}.pem |
| 522 | ssl_key = </run/credentials/dovecot2.service/${subdomain}.key.pem | 578 | ssl_key = </run/credentials/dovecot.service/${subdomain}.key.pem |
| 523 | } | 579 | } |
| 524 | '') ["imap.${domain}" domain] | 580 | '') ["imap.${domain}" domain] |
| 525 | ) emailDomains} | 581 | ) emailDomains} |
| @@ -540,10 +596,10 @@ in { | |||
| 540 | auth_debug = yes | 596 | auth_debug = yes |
| 541 | 597 | ||
| 542 | service auth { | 598 | service auth { |
| 543 | user = dovecot2 | 599 | user = ${config.services.dovecot2.user} |
| 544 | } | 600 | } |
| 545 | service auth-worker { | 601 | service auth-worker { |
| 546 | user = dovecot2 | 602 | user = ${config.services.dovecot2.user} |
| 547 | } | 603 | } |
| 548 | 604 | ||
| 549 | userdb { | 605 | userdb { |
| @@ -564,7 +620,7 @@ in { | |||
| 564 | args = ${pkgs.writeText "dovecot-sql.conf" '' | 620 | args = ${pkgs.writeText "dovecot-sql.conf" '' |
| 565 | driver = pgsql | 621 | driver = pgsql |
| 566 | connect = dbname=email | 622 | 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 | 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 |
| 568 | ''} | 624 | ''} |
| 569 | 625 | ||
| 570 | skip = never | 626 | skip = never |
| @@ -634,7 +690,7 @@ in { | |||
| 634 | quota_status_success = DUNNO | 690 | quota_status_success = DUNNO |
| 635 | quota_status_nouser = DUNNO | 691 | quota_status_nouser = DUNNO |
| 636 | quota_grace = 10%% | 692 | quota_grace = 10%% |
| 637 | 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} |
| 638 | quota_vsizes = yes | 694 | quota_vsizes = yes |
| 639 | } | 695 | } |
| 640 | 696 | ||
| @@ -670,13 +726,16 @@ in { | |||
| 670 | } | 726 | } |
| 671 | 727 | ||
| 672 | plugin { | 728 | plugin { |
| 673 | plugin = fts fts_xapian | 729 | fts = flatcurve |
| 674 | fts = xapian | ||
| 675 | fts_xapian = partial=2 full=20 attachments=1 verbose=1 | ||
| 676 | 730 | ||
| 677 | fts_autoindex = yes | 731 | fts_languages = en de |
| 732 | fts_tokenizers = generic email-address | ||
| 678 | 733 | ||
| 679 | 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 | ||
| 680 | } | 739 | } |
| 681 | 740 | ||
| 682 | service indexer-worker { | 741 | service indexer-worker { |
| @@ -685,30 +744,6 @@ in { | |||
| 685 | ''; | 744 | ''; |
| 686 | }; | 745 | }; |
| 687 | 746 | ||
| 688 | systemd.services.dovecot-fts-xapian-optimize = { | ||
| 689 | description = "Optimize dovecot indices for fts_xapian"; | ||
| 690 | requisite = [ "dovecot2.service" ]; | ||
| 691 | after = [ "dovecot2.service" ]; | ||
| 692 | startAt = "*-*-* 22:00:00 Europe/Berlin"; | ||
| 693 | serviceConfig = { | ||
| 694 | Type = "oneshot"; | ||
| 695 | ExecStart = "${pkgs.dovecot}/bin/doveadm fts optimize -A"; | ||
| 696 | PrivateDevices = true; | ||
| 697 | PrivateNetwork = true; | ||
| 698 | ProtectKernelTunables = true; | ||
| 699 | ProtectKernelModules = true; | ||
| 700 | ProtectControlGroups = true; | ||
| 701 | ProtectHome = true; | ||
| 702 | ProtectSystem = true; | ||
| 703 | PrivateTmp = true; | ||
| 704 | }; | ||
| 705 | }; | ||
| 706 | systemd.timers.dovecot-fts-xapian-optimize = { | ||
| 707 | timerConfig = { | ||
| 708 | RandomizedDelaySec = 4 * 3600; | ||
| 709 | }; | ||
| 710 | }; | ||
| 711 | |||
| 712 | environment.etc = { | 747 | environment.etc = { |
| 713 | "dovecot/sieve_before.d/tag-junk.sieve".text = '' | 748 | "dovecot/sieve_before.d/tag-junk.sieve".text = '' |
| 714 | require ["imap4flags"]; | 749 | require ["imap4flags"]; |
| @@ -753,31 +788,29 @@ in { | |||
| 753 | 788 | ||
| 754 | security.acme.rfc2136Domains = { | 789 | security.acme.rfc2136Domains = { |
| 755 | "surtr.yggdrasil.li" = { | 790 | "surtr.yggdrasil.li" = { |
| 756 | restartUnits = [ "postfix.service" "dovecot2.service" ]; | 791 | restartUnits = [ "postfix.service" "dovecot.service" ]; |
| 757 | }; | 792 | }; |
| 758 | } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains) | 793 | } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains) |
| 759 | // listToAttrs (concatMap (domain: [ | 794 | // listToAttrs (concatMap (domain: [ |
| 760 | (nameValuePair domain { restartUnits = ["postfix.service" "dovecot2.service"]; }) | 795 | (nameValuePair domain { restartUnits = ["postfix.service" "dovecot.service"]; }) |
| 761 | (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; }) | 796 | (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; }) |
| 762 | (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; }) | 797 | (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; }) |
| 763 | (nameValuePair "imap.${domain}" { restartUnits = ["dovecot2.service"]; }) | 798 | (nameValuePair "imap.${domain}" { restartUnits = ["dovecot.service"]; }) |
| 764 | (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; }) | 799 | (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; }) |
| 765 | ]) emailDomains); | 800 | ]) emailDomains); |
| 766 | 801 | ||
| 767 | systemd.services.postfix = { | 802 | systemd.services.postfix = { |
| 768 | serviceConfig.LoadCredential = [ | 803 | serviceConfig.LoadCredential = let |
| 769 | "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"; |
| 770 | "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem" | 805 | in [ |
| 771 | ] ++ concatMap (domain: | 806 | (tlsCredential "surtr.yggdrasil.li") |
| 772 | map (subdomain: "${subdomain}.full.pem:${config.security.acme.certs.${subdomain}.directory}/full.pem") | 807 | ] ++ concatMap (domain: map tlsCredential [domain "mailin.${domain}" "mailsub.${domain}"]) emailDomains; |
| 773 | [domain "mailin.${domain}" "mailsub.${domain}"] | ||
| 774 | ) emailDomains; | ||
| 775 | }; | 808 | }; |
| 776 | 809 | ||
| 777 | systemd.services.dovecot2 = { | 810 | systemd.services.dovecot = { |
| 778 | preStart = '' | 811 | preStart = '' |
| 779 | 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 |
| 780 | ${pkgs.dovecot_pigeonhole}/bin/sievec $f | 813 | ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f |
| 781 | done | 814 | done |
| 782 | ''; | 815 | ''; |
| 783 | 816 | ||
| @@ -844,15 +877,16 @@ in { | |||
| 844 | charset utf-8; | 877 | charset utf-8; |
| 845 | source_charset utf-8; | 878 | source_charset utf-8; |
| 846 | ''; | 879 | ''; |
| 847 | root = pkgs.runCommand "mta-sts.${domain}" {} '' | 880 | root = pkgs.writeTextFile { |
| 848 | mkdir -p $out/.well-known | 881 | name = "mta-sts.${domain}"; |
| 849 | cp ${pkgs.writeText "mta-sts.${domain}.txt" '' | 882 | destination = "/.well-known/mta-sts.txt"; |
| 883 | text = '' | ||
| 850 | version: STSv1 | 884 | version: STSv1 |
| 851 | mode: enforce | 885 | mode: enforce |
| 852 | max_age: 2419200 | 886 | max_age: 2419200 |
| 853 | mx: mailin.${domain} | 887 | mx: mailin.${domain} |
| 854 | ''} $out/.well-known/mta-sts.txt | 888 | ''; |
| 855 | ''; | 889 | }; |
| 856 | }; | 890 | }; |
| 857 | }) emailDomains); | 891 | }) emailDomains); |
| 858 | }; | 892 | }; |
| @@ -869,7 +903,7 @@ in { | |||
| 869 | systemd.services.spm = { | 903 | systemd.services.spm = { |
| 870 | serviceConfig = { | 904 | serviceConfig = { |
| 871 | Type = "notify"; | 905 | Type = "notify"; |
| 872 | ExecStart = "${pkgs.spm}/bin/spm-server"; | 906 | ExecStart = getExe' pkgs.spm "spm-server"; |
| 873 | User = "spm"; | 907 | User = "spm"; |
| 874 | Group = "spm"; | 908 | Group = "spm"; |
| 875 | 909 | ||
| @@ -927,7 +961,7 @@ in { | |||
| 927 | serviceConfig = { | 961 | serviceConfig = { |
| 928 | Type = "notify"; | 962 | Type = "notify"; |
| 929 | 963 | ||
| 930 | ExecStart = "${ccert-policy-server}/bin/ccert-policy-server"; | 964 | ExecStart = getExe' ccert-policy-server "ccert-policy-server"; |
| 931 | 965 | ||
| 932 | Environment = [ | 966 | Environment = [ |
| 933 | "PGDATABASE=email" | 967 | "PGDATABASE=email" |
| @@ -960,6 +994,53 @@ in { | |||
| 960 | }; | 994 | }; |
| 961 | users.groups."postfix-ccert-sender-policy" = {}; | 995 | users.groups."postfix-ccert-sender-policy" = {}; |
| 962 | 996 | ||
| 997 | systemd.sockets."postfix-internal-policy" = { | ||
| 998 | requiredBy = ["postfix.service"]; | ||
| 999 | wants = ["postfix-internal-policy.service"]; | ||
| 1000 | socketConfig = { | ||
| 1001 | ListenStream = "/run/postfix-internal-policy.sock"; | ||
| 1002 | }; | ||
| 1003 | }; | ||
| 1004 | systemd.services."postfix-internal-policy" = { | ||
| 1005 | after = [ "postgresql.service" ]; | ||
| 1006 | bindsTo = [ "postgresql.service" ]; | ||
| 1007 | |||
| 1008 | serviceConfig = { | ||
| 1009 | Type = "notify"; | ||
| 1010 | |||
| 1011 | ExecStart = lib.getExe internal-policy-server; | ||
| 1012 | |||
| 1013 | Environment = [ | ||
| 1014 | "PGDATABASE=email" | ||
| 1015 | ]; | ||
| 1016 | |||
| 1017 | DynamicUser = false; | ||
| 1018 | User = "postfix-internal-policy"; | ||
| 1019 | Group = "postfix-internal-policy"; | ||
| 1020 | ProtectSystem = "strict"; | ||
| 1021 | SystemCallFilter = "@system-service"; | ||
| 1022 | NoNewPrivileges = true; | ||
| 1023 | ProtectKernelTunables = true; | ||
| 1024 | ProtectKernelModules = true; | ||
| 1025 | ProtectKernelLogs = true; | ||
| 1026 | ProtectControlGroups = true; | ||
| 1027 | MemoryDenyWriteExecute = true; | ||
| 1028 | RestrictSUIDSGID = true; | ||
| 1029 | KeyringMode = "private"; | ||
| 1030 | ProtectClock = true; | ||
| 1031 | RestrictRealtime = true; | ||
| 1032 | PrivateDevices = true; | ||
| 1033 | PrivateTmp = true; | ||
| 1034 | ProtectHostname = true; | ||
| 1035 | ReadWritePaths = ["/run/postgresql"]; | ||
| 1036 | }; | ||
| 1037 | }; | ||
| 1038 | users.users."postfix-internal-policy" = { | ||
| 1039 | isSystemUser = true; | ||
| 1040 | group = "postfix-internal-policy"; | ||
| 1041 | }; | ||
| 1042 | users.groups."postfix-internal-policy" = {}; | ||
| 1043 | |||
| 963 | services.postfwd = { | 1044 | services.postfwd = { |
| 964 | enable = true; | 1045 | enable = true; |
| 965 | cache = false; | 1046 | cache = false; |
