{ config, pkgs, lib, ... }: with lib; let compileSieve = name: text: pkgs.runCommand name {} '' mkdir $out cp ${pkgs.writeText name '' ''} $out/${name} ${pkgs.dovecot_pigeonhole}/bin/sievec $out/${name} ''; in { config = { services.postfix = { enable = true; hostname = "surtr.yggdrasil.li"; recipientDelimiter = "+"; setSendmail = true; postmasterAlias = ""; rootAlias = ""; extraAliases = ""; destination = []; sslCert = "/run/credentials/postfix.service/surtr.yggdrasil.li.pem"; sslKey = "/run/credentials/postfix.service/surtr.yggdrasil.li.key.pem"; networks = ["127.0.0.0/8" "[::ffff:127.0.0.0]/104" "[::1]/128" "10.141.0.0/16"]; config = let relay_ccert = "texthash:${pkgs.writeText "relay_ccert" ""}"; in { #the dh params smtpd_tls_dh1024_param_file = toString config.security.dhparams.params."postfix-1024".path; smtpd_tls_dh512_param_file = toString config.security.dhparams.params."postfix-512".path; #enable ECDH smtpd_tls_eecdh_grade = "strong"; #enabled SSL protocols, don't allow SSLv2 and SSLv3 smtpd_tls_protocols = ["!SSLv2" "!SSLv3" "!TLSv1" "!TLSv1.1" "!TLSv1.2"]; smtpd_tls_mandatory_protocols = ["!SSLv2" "!SSLv3" "!TLSv1" "!TLSv1.1" "!TLSv1.2"]; #allowed ciphers for smtpd_tls_security_level=encrypt smtpd_tls_mandatory_ciphers = "high"; #allowed ciphers for smtpd_tls_security_level=may #smtpd_tls_ciphers = high #enforce the server cipher preference tls_preempt_cipherlist = true; #disable following ciphers for smtpd_tls_security_level=encrypt smtpd_tls_mandatory_exclude_ciphers = ["aNULL" "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL"]; #disable following ciphers for smtpd_tls_security_level=may smtpd_tls_exclude_ciphers = ["aNULL" "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL"]; #enable TLS logging to see the ciphers for inbound connections smtpd_tls_loglevel = "1"; #enable TLS logging to see the ciphers for outbound connections smtp_tls_loglevel = "1"; smtpd_tls_received_header = true; smtpd_tls_ask_ccert = true; smtpd_tls_CAfile = toString ./ca/ca.crt; smtp_tls_security_level = "dane"; smtp_dns_support_level = "dnssec"; tls_server_sni_maps = ''texthash:${pkgs.writeText "sni" '' bouncy.email /run/credentials/postfix.service/bouncy.email.full.pem mailin.bouncy.email /run/credentials/postfix.service/mailin.bouncy.email.full.pem mailsub.bouncy.email /run/credentials/postfix.service/mailsub.bouncy.email.full.pem .bouncy.email /run/credentials/postfix.service/bouncy.email.full.pem ''}''; local_recipient_maps = ""; # 10 GiB message_size_limit = "10737418240"; # 10 GiB mailbox_size_limit = "10737418240"; smtpd_delay_reject = true; smtpd_helo_required = true; smtpd_helo_restrictions = "permit"; smtpd_recipient_restrictions = [ "reject_unauth_pipelining" "reject_non_fqdn_recipient" "reject_unknown_recipient_domain" "permit_mynetworks" "check_ccert_access ${relay_ccert}" "reject_non_fqdn_helo_hostname" "reject_invalid_helo_hostname" "reject_unauth_destination" "reject_unknown_recipient_domain" "reject_unverified_recipient" ]; smtpd_relay_restrictions = [ "permit_mynetworks" "check_ccert_access ${relay_ccert}" "reject_unauth_destination" ]; propagate_unmatched_extensions = ["canonical" "virtual" "alias"]; smtpd_authorized_verp_clients = "$authorized_verp_clients"; authorized_verp_clients = "$mynetworks"; milter_default_action = "accept"; smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"]; non_smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"]; alias_maps = ""; queue_run_delay = "10s"; minimal_backoff_time = "1m"; maximal_backoff_time = "10m"; maximal_queue_lifetime = "100m"; bounce_queue_lifetime = "20m"; smtpd_discard_ehlo_keyword_address_maps = "cidr:${pkgs.writeText "esmtp_access" '' # Allow DSN requests from local subnet only 192.168.0.0/16 silent-discard 172.16.0.0/12 silent-discard 10.0.0.0/8 silent-discard 0.0.0.0/0 silent-discard, dsn fd00::/8 silent-discard ::/0 silent-discard, dsn ''}"; sender_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.forwardPort}"; sender_canonical_classes = "envelope_sender"; recipient_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.reversePort}"; recipient_canonical_classes = ["envelope_recipient" "header_recipient"]; virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" '' dbname = emails table = virtual_mailbox_domain select_field = domain where_field = domain ''}''; virtual_mailbox_maps = ''pgsql:${pkgs.writeText "virtual_mailbox_maps.cf" '' dbname = emails table = virtual_mailbox_mapping select_field = mailbox where_field = lookup ''}''; }; masterConfig = { smtps = { type = "inet"; private = false; command = "smtpd"; args = [ "-o" "smtpd_tls_wrappermode=yes" "-o" "smtpd_tls_ask_ccert=yes" "-o" "smtpd_tls_req_ccert=yes" "-o" "smtpd_client_restrictions=permit_tls_all_clientcerts,reject" "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" "-o" "smtpd_recipient_restrictions=reject_unauth_pipelining,reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_tls_all_clientcerts,reject" "-o" "milter_macro_daemon_name=surtr.yggdrasil.li" ]; }; }; }; services.postsrsd = { enable = true; domain = "surtr.yggdrasil.li"; separator = "+"; excludeDomains = [ "surtr.yggdrasil.li" ".bouncy.email" "bouncy.email" ]; }; services.opendkim = { enable = true; user = "postfix"; group = "postfix"; socket = "local:/run/opendkim/opendkim.sock"; domains = ''csl:${concatStringsSep "," ["surtr.yggdrasil.li" "bouncy.email"]}''; selector = "surtr"; configFile = builtins.toFile "opendkim.conf" '' Syslog true MTA surtr.yggdrasil.li MTACommand ${config.security.wrapperDir}/sendmail LogResults true ''; }; services.rspamd = { enable = true; workers = { controller = {}; external = { type = "rspamd_proxy"; bindSockets = [ { mode = "0660"; socket = "/run/rspamd/rspamd-milter.sock"; owner = config.services.rspamd.user; group = config.services.rspamd.group; } ]; extraConfig = '' milter = yes; upstream "local" { default = yes; self_scan = yes; } ''; }; }; locals = { "milter_headers.conf".text = '' use = ["authentication-results", "x-spamd-result", "x-rspamd-queue-id", "x-rspamd-server", "x-spam-level", "x-spam-status"]; extended_headers_rcpt = []; ''; "actions.conf".text = '' reject = 15; add_header = 10; greylist = 5; ''; "groups.conf".text = '' symbols { "BAYES_SPAM" { weight = 2.0; } } ''; "dmarc.conf".text = '' reporting = true; send_reports = true; report_settings { org_name = "Yggdrasil.li"; domain = "yggdrasil.li"; email = "postmaster@yggdrasil.li"; } ''; "redis.conf".text = '' servers = "${config.services.redis.servers.rspamd.unixSocket}"; ''; "dkim_signing.conf".text = "enabled = false;"; "neural.conf".text = "enabled = false;"; "classifier-bayes.conf".text = '' enable = true; expire = 8640000; new_schema = true; backend = "redis"; per_user = true; min_learns = 0; autolearn = [0, 10]; statfile { symbol = "BAYES_HAM"; spam = false; } statfile { symbol = "BAYES_SPAM"; spam = true; } ''; # "redirectors.inc".text = '' # visit.creeper.host # ''; }; }; users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user ]; services.redis.servers.rspamd.enable = true; users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ]; services.dovecot2 = { enable = true; sslServerCert = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.pem"; sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; sslCACert = toString ./ca/ca.crt; mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8"; modules = with pkgs; [ dovecot_pigeonhole ]; protocols = [ "lmtp" "sieve" ]; extraConfig = '' mail_home = /var/lib/mail/%u local_name imap.bouncy.email { ssl_cert = <$/run/credentials/dovecot2.service/imap.bouncy.email.pem ssl_key = <$/run/credentials/dovecot2.service/imap.bouncy.email.key.pem } local_name bouncy.email { ssl_cert = <$/run/credentials/dovecot2.service/bouncy.email.pem ssl_key = <$/run/credentials/dovecot2.service/bouncy.email.key.pem } ssl_require_crl = yes ssl_verify_client_cert = yes auth_ssl_username_from_cert = yes auth_mechanisms = external userdb sql { args = ${pkgs.writeText "dovecot-sql.conf" '' driver = pgsql connect = host=localhost dbname=email user_query = SELECT mailbox AS user, quota_rule FROM mailbox WHERE mailbox = '%u' ''} default_fields = uid=dovecot2 gid=dovecot2 } mail_plugins = $mail_plugins quota mailbox_list_index = yes postmaster_address = postmaster@yggdrasil.li recipient_delimiter = + service lmtp { vsz_limit = 1G unix_listener /run/postfix/dovecot-lmtp { mode = 0600 user = postfix group = postfix } } namespace inbox { separator = / inbox = yes prefix = } plugin { quota = maildir quota_rule = *:storage=1GB quota_rule2 = Trash:storage=+10%% quota_status_overquota = "552 5.2.2 Mailbox is full" quota_status_success = DUNNO quota_status_nouser = DUNNO quota_grace = 10%% } protocol imap { mail_max_userip_connections = 50 mail_plugins = $mail_plugins imap_quota imap_sieve } service imap-login { inet_listener imap { port = 0 } } service managesieve-login { inet_listener sieve { port = 4190 ssl = yes } } plugin { sieve_plugins = sieve_imapsieve sieve_redirect_envelope_from = orig_recipient sieve_before = /etc/dovecot/sieve_before.d } ''; }; environment.etc."dovecot/sieve_before.d/tag-junk.sieve".text = '' require ["imap4flags"]; if header :contains "X-Spam-Flag" "YES" { addflag ["\\Junk"]; } ''; security.dhparams = { params = { "postfix-512".bits = 512; "postfix-1024".bits = 2048; }; }; security.acme.domains = { "bouncy.email" = {}; "mailin.bouncy.email" = {}; "mailsub.bouncy.email" = {}; "imap.bouncy.email" = {}; "surtr.yggdrasil.li" = {}; }; systemd.services.postfix = { serviceConfig.LoadCredential = [ "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem" "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem" "bouncy.email.full.pem:${config.security.acme.certs."bouncy.email".directory}/full.pem" "mailin.bouncy.email.full.pem:${config.security.acme.certs."mailin.bouncy.email".directory}/full.pem" "mailsub.bouncy.email.full.pem:${config.security.acme.certs."mailsub.bouncy.email".directory}/full.pem" ]; }; systemd.services.dovecot2 = { preStart = '' for f in /etc/dovecot/sieve_before.d/*.sieve; do ${pkgs.dovecot_pigeonhole}/bin/sievec $f done ''; serviceConfig = { LoadCredential = [ "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem" "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem" "bouncy.email.key.pem:${config.security.acme.certs."bouncy.email".directory}/key.pem" "bouncy.email.pem:${config.security.acme.certs."bouncy.email".directory}/fullchain.pem" "imap.bouncy.email.key.pem:${config.security.acme.certs."imap.bouncy.email".directory}/key.pem" "imap.bouncy.email.pem:${config.security.acme.certs."imap.bouncy.email".directory}/fullchain.pem" ]; StateDirectory = "mail"; }; }; }; }