summaryrefslogtreecommitdiff
path: root/hosts
diff options
context:
space:
mode:
Diffstat (limited to 'hosts')
-rw-r--r--hosts/surtr/email/default.nix368
1 files changed, 179 insertions, 189 deletions
diff --git a/hosts/surtr/email/default.nix b/hosts/surtr/email/default.nix
index 4243366c..e688f7d2 100644
--- a/hosts/surtr/email/default.nix
+++ b/hosts/surtr/email/default.nix
@@ -291,7 +291,7 @@ in {
291 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; 291 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp";
292 smtputf8_enable = false; 292 smtputf8_enable = false;
293 293
294 authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.user}= }"; 294 authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.settings.mail_uid}= }";
295 authorized_flush_users = "inline:{ root= }"; 295 authorized_flush_users = "inline:{ root= }";
296 authorized_mailq_users = "inline:{ root= }"; 296 authorized_mailq_users = "inline:{ root= }";
297 297
@@ -528,215 +528,211 @@ in {
528 }; 528 };
529 }; 529 };
530 530
531 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user config.services.dovecot2.user ]; 531 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user config.services.dovecot2.settings.mail_uid ];
532 532
533 services.redis.servers.rspamd.enable = true; 533 services.redis.servers.rspamd.enable = true;
534 534
535 users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ]; 535 users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ];
536 536
537 environment.systemPackages = with pkgs; [ dovecot_pigeonhole dovecot-fts-flatcurve ]; 537 environment.systemPackages = with pkgs; [ dovecot_pigeonhole ];
538 services.dovecot2 = { 538 services.dovecot2 = {
539 package = pkgs.dovecot;
539 enable = true; 540 enable = true;
540 enablePAM = false; 541 enablePAM = false;
541 sslServerCert = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem"; 542 settings = {
542 sslServerKey = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem"; 543 dovecot_config_version = "2.4.2";
543 sslCACert = toString ./ca/ca.crt; 544 dovecot_storage_version = "2.4.0";
544 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u";
545 mailPlugins.globally.enable = [ "fts" "fts_flatcurve" ];
546 protocols = [ "lmtp" "sieve" ];
547 sieve = {
548 extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"];
549 globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"];
550 };
551 extraConfig = let
552 dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" ''
553 driver = pgsql
554 connect = dbname=email
555 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'
556 user_query = SELECT "user", quota_rule, '${config.services.dovecot2.user}' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n'
557 iterate_query = SELECT "user" FROM imap_user
558 '';
559 in ''
560 mail_home = /var/lib/mail/%u
561
562 mail_plugins = $mail_plugins quota fts fts_flatcurve
563
564 first_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
565 last_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
566 first_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
567 last_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
568
569 ${concatMapStringsSep "\n\n" (domain:
570 concatMapStringsSep "\n" (subdomain: ''
571 local_name ${subdomain} {
572 ssl_cert = </run/credentials/dovecot.service/${subdomain}.pem
573 ssl_key = </run/credentials/dovecot.service/${subdomain}.key.pem
574 }
575 '') ["imap.${domain}" domain]
576 ) emailDomains}
577
578 ssl_require_crl = no
579 ssl_verify_client_cert = yes
580
581 ssl_min_protocol = TLSv1.2
582 ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
583 ssl_prefer_server_ciphers = no
584
585 auth_ssl_username_from_cert = yes
586 ssl_cert_username_field = commonName
587 auth_mechanisms = plain login external
588 545
589 auth_verbose = yes 546 sql_driver = "pgsql";
590 verbose_ssl = yes 547 "pgsql /run/postgresql".parameters = {
591 auth_debug = yes 548 dbname = "email";
592 549 };
593 service auth {
594 user = ${config.services.dovecot2.user}
595 }
596 service auth-worker {
597 user = ${config.services.dovecot2.user}
598 }
599
600 userdb {
601 driver = prefetch
602 }
603 userdb {
604 driver = sql
605 args = ${dovecotSqlConf}
606 }
607 passdb {
608 driver = sql
609 args = ${dovecotSqlConf}
610 }
611 550
612 protocol lmtp { 551 protocols = {
613 userdb { 552 imap = true;
614 driver = sql 553 lmtp = true;
615 args = ${pkgs.writeText "dovecot-sql.conf" '' 554 sieve = true;
616 driver = pgsql 555 };
617 connect = dbname=email
618 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
619 ''}
620
621 skip = never
622 result_failure = return-fail
623 result_internalfail = return-fail
624 }
625 556
626 mail_plugins = $mail_plugins sieve 557 mail_plugins = {
627 } 558 quota = true;
559 fts = true;
560 fts_flatcurve = true;
561 };
628 562
629 mailbox_list_index = yes 563 mail_uid = "dovecot2";
630 postmaster_address = postmaster@yggdrasil.li 564 mail_gid = "dovecot2";
631 recipient_delimiter = 565
632 auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-+_@ 566 first_valid_uid = config.ids.uids.dovecot2;
567 last_valid_uid = config.ids.uids.dovecot2;
568 first_valid_gid = config.ids.gids.dovecot2;
569 last_valid_gid = config.ids.gids.dovecot2;
570
571 mail_driver = "maildir";
572 mail_path = "/var/lib/mail/%{user}/maildir";
573 mail_index_path = "/var/lib/dovecot/indices/%{user}";
574 ssl_server_ca_file = ./ca/ca.crt;
575 ssl_server_key_file = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem";
576 ssl_server_cert_file = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem";
577
578 mail_home = "/var/lib/mail/%{user}";
579
580 ssl_server_require_crl = false;
581 ssl_server_request_client_cert = true;
582
583 ssl_min_protocol = "TLSv1.2";
584 ssl_curve_list = "X25519MLKEM768:X25519";
585 ssl_cipher_list = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305";
586
587 auth_ssl_username_from_cert = "yes";
588 ssl_server_cert_username_field = "commonName";
589 auth_mechanisms = ["plain" "login" "external"];
590
591 log_debug = "category=ssl OR category=auth";
592 auth_verbose = true;
593
594 "service auth-worker".user = "$SET:default_internal_user";
595 "userdb prefetch" = {};
596 "userdb sql" = {
597 sql_query = "SELECT \"user\", quota_rule, '${config.services.dovecot2.settings.mail_uid}' as uid, 'dovecot2' as gid FROM imap_user WHERE \"user\" = '%{user | username}'";
598 sql_iterate_query = "SELECT \"user\" FROM imap_user";
599 fields = {
600 uid = "$SET:default_internal_user";
601 gid = "$SET:default_internal_user";
602 };
603 };
604 "passdb sql" = {
605 sql_query = ''
606 SELECT (CASE WHEN '%{cert}' = 'valid' AND '%{mechanism}' = 'EXTERNAL' THEN NULL ELSE "password" END) as password, (CASE WHEN '%{cert}' = 'valid' AND '%{mechanism}' = 'EXTERNAL' THEN true WHEN password IS NULL THEN true ELSE NULL END) as nopassword, "user", quota_rule, '${config.services.dovecot2.settings.mail_uid}' as uid, '${config.services.dovecot2.settings.mail_gid}' as gid FROM imap_user WHERE "user" = '%{user | username}'
607 '';
608 };
633 609
634 service lmtp { 610 "protocol lmtp" = {
635 vsz_limit = 1G 611 mail_plugins.sieve = true;
612 "userdb sql-lmtp" = {
613 driver = "sql";
614 sql_query = ''
615 SELECT DISTINCT ON (extension IS NULL, local IS NULL) "user", quota_rule, '${config.services.dovecot2.settings.mail_uid}' as uid, '${config.services.dovecot2.settings.mail_gid}' as gid FROM lmtp_mapping WHERE CASE WHEN extension IS NOT NULL AND local IS NOT NULL THEN ('%{user | username}' :: citext) = local || '+' || extension AND domain = ('%{user | domain}' :: citext) WHEN local IS NOT NULL THEN (local = ('%{user | username}' :: citext) OR ('%{user | username}' :: citext) ILIKE local || '+%%') AND domain = ('%{user | domain}' :: citext) WHEN extension IS NOT NULL THEN ('%{user | username}' :: citext) ILIKE '%%+' || extension AND domain = ('%{user | domain}' :: citext) ELSE domain = ('%{user | domain}' :: citext) END ORDER BY (extension IS NULL) ASC, (local IS NULL) ASC
616 '';
636 617
637 unix_listener /run/dovecot-lmtp { 618 skip = "never";
638 mode = 0600 619 result_failure = "return-fail";
639 user = postfix 620 result_internalfail = "return-fail";
640 group = postfix 621 };
641 } 622 };
642 }
643 service auth {
644 vsz_limit = 2G
645 623
646 unix_listener /run/dovecot-sasl { 624 mailbox_list_index = true;
647 mode = 0600 625 mailbox_list_utf8 = true;
648 user = postfix 626 postmaster_address = "postmaster@yggdrasil.li";
649 group = postfix 627 recipient_delimiter = null;
650 } 628 auth_username_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-+_@";
651 }
652 629
653 namespace inbox { 630 "service lmtp" = {
654 separator = / 631 vsz_limit = "1G";
655 inbox = yes
656 prefix =
657 632
658 mailbox Trash { 633 "unix_listener /run/dovecot-lmtp" = {
659 auto = no 634 mode = "0600";
660 special_use = \Trash 635 user = "postfix";
661 } 636 group = "postfix";
662 mailbox Junk { 637 };
663 auto = no 638 };
664 special_use = \Junk 639 "service auth" = {
665 } 640 vsz_limit = "2G";
666 mailbox Drafts {
667 auto = no
668 special_use = \Drafts
669 }
670 mailbox Sent {
671 auto = subscribe
672 special_use = \Sent
673 }
674 mailbox "Sent Messages" {
675 auto = no
676 special_use = \Sent
677 }
678 }
679
680 plugin {
681 quota = count
682 quota_rule = *:storage=1GB
683 quota_rule2 = Trash:storage=+10%%
684 quota_status_overquota = "552 5.2.2 Mailbox is full"
685 quota_status_success = DUNNO
686 quota_status_nouser = DUNNO
687 quota_grace = 10%%
688 quota_max_mail_size = ${toString config.services.postfix.settings.main.message_size_limit}
689 quota_vsizes = yes
690 }
691
692 protocol imap {
693 mail_max_userip_connections = 50
694 mail_plugins = $mail_plugins imap_quota imap_sieve
695 }
696 641
697 service imap-login { 642 "unix_listener /run/dovecot-sasl" = {
698 inet_listener imap { 643 mode = "0600";
699 port = 0 644 user = "postfix";
700 } 645 group = "postfix";
701 } 646 };
647 };
702 648
703 service managesieve-login { 649 quota_storage_size = "1G";
704 inet_listener sieve { 650 "namespace inbox" = {
705 port = 4190 651 separator = "/";
706 } 652 inbox = true;
707 }
708 653
709 plugin { 654 "mailbox Trash" = {
710 sieve_plugins = sieve_imapsieve sieve_extprograms 655 auto = false;
711 sieve = file:~/sieve;active=~/dovecot.sieve 656 special_use = "\\Trash";
712 sieve_redirect_envelope_from = orig_recipient 657 quota_storage_percentage = "110";
713 sieve_before = /etc/dovecot/sieve_before.d 658 };
659 "mailbox Junk" = {
660 auto = false;
661 special_use = "\\Junk";
662 };
663 "mailbox Drafts" = {
664 auto = false;
665 special_use = "\\Drafts";
666 };
667 "mailbox Sent" = {
668 auto = "subscribe";
669 special_use = "\\Sent";
670 };
671 "mailbox \"Sent Messages\"" = {
672 auto = false;
673 special_use = "\\Sent";
674 };
675 };
714 676
715 sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment 677 quota_status_overquota = "552 5.2.2 Mailbox is full";
716 sieve_pipe_bin_dir = ${dovecotSievePipeBin}/pipe/bin 678 quota_status_success = "DUNNO";
679 quota_status_nouser = "DUNNO";
680 quota_storage_grace = "100M";
681 quota_mail_size = 10 * 1024 * 1024 * 1024;
717 682
718 imapsieve_mailbox1_name = * 683 sieve_plugins = {
719 imapsieve_mailbox1_causes = FLAG 684 "sieve_imapsieve" = true;
720 imapsieve_mailbox1_before = /etc/dovecot/sieve_flag.d/learn-junk.sieve 685 "sieve_extprograms" = true;
721 } 686 };
687 sieve_redirect_envelope_from = "orig_recipient";
688 sieve_extensions = {
689 imapsieve = true;
690 vacation-seconds = true;
691 "vnd.dovecot.debug" = true;
692 };
693 sieve_global_extensions = {
694 "vnd.dovecot.pipe" = true;
695 "vnd.dovecot.environment" = true;
696 };
697 sieve_pipe_bin_dir = "${dovecotSievePipeBin}/pipe/bin";
722 698
723 plugin { 699 "sieve_script before" = {
724 fts = flatcurve 700 type = "before";
701 path = "/etc/dovecot/sieve_before.d";
702 };
703 "sieve_script flag" = {
704 type = "before";
705 cause.flag = true;
706 path = "/etc/dovecot/sieve_flag.d";
707 };
725 708
726 fts_languages = en de 709 "fts flatcurve" = {
727 fts_tokenizers = generic email-address 710 autoindex = true;
711 };
712 language_tokenizers = ["generic" "email-address"];
713 language_filters = ["normalizer-icu" "snowball" "stopwords"];
714 "language en" = {
715 default = true;
716 filters = ["lowercase" "snowball" "stopwords"];
717 };
718 "language de" = {};
728 719
729 fts_tokenizer_email_address = maxlen=100 720 "protocol imap" = {
730 fts_tokenizer_generic = algorithm=simple maxlen=30 721 mail_max_userip_connections = 50;
722 mail_plugins = {
723 imap_quota = true;
724 imap_sieve = true;
725 };
726 };
731 727
732 fts_filters = normalizer-icu snowball stopwords 728 "service imap-login"."inet_listener imap".port = 0;
733 fts_filters_en = lowercase snowball stopwords 729 "service managesieve-login"."inet_listener sieve".port = 4190;
734 }
735 730
736 service indexer-worker { 731 "service indexer-worker".vsz_limit = 1024 * 1024 * 1024;
737 vsz_limit = ${toString (1024 * 1024 * 1024)} 732 } // (genAttrs' (concatMap (domain: ["imap.${domain}" domain]) emailDomains) (subdomain: nameValuePair "local_name ${subdomain}" {
738 } 733 ssl_server_key_file = "/run/credentials/dovecot.service/${subdomain}.key.pem";
739 ''; 734 ssl_server_cert_file = "/run/credentials/dovecot.service/${subdomain}.pem";
735 }));
740 }; 736 };
741 737
742 environment.etc = { 738 environment.etc = {
@@ -793,12 +789,6 @@ in {
793 }; 789 };
794 790
795 systemd.services.dovecot = { 791 systemd.services.dovecot = {
796 preStart = ''
797 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do
798 ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f
799 done
800 '';
801
802 serviceConfig = { 792 serviceConfig = {
803 LoadCredential = [ 793 LoadCredential = [
804 "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem" 794 "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem"