{ config, pkgs, lib, flake, flakeInputs, ... }: with lib; let dovecotSievePipeBin = pkgs.stdenv.mkDerivation { name = "dovecot-sieve-pipe-bin"; src = ./dovecot-pipe-bin; buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ]; buildCommand = '' mkdir -p $out/pipe/bin cp $src/* $out/pipe/bin/ chmod a+x $out/pipe/bin/* patchShebangs $out/pipe/bin for file in $out/pipe/bin/*; do wrapProgram $file \ --set PATH "${makeBinPath (with pkgs; [coreutils rspamd])}" done ''; }; ccert-policy-server = with pkgs.poetry2nix; mkPoetryApplication { python = pkgs.python311; projectDir = cleanPythonSources { src = ./ccert-policy-server; }; overrides = overrides.withDefaults (self: super: { systemd-python = super.systemd-python.overridePythonAttrs (oldAttrs: { buildInputs = (oldAttrs.buildInputs or []) ++ [ super.setuptools ]; }); }); }; internal-policy-server = let workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./internal-policy-server; }; pythonSet = flake.lib.pythonSet { inherit pkgs; python = pkgs.python312; overlay = workspace.mkPyprojectOverlay { sourcePreference = "wheel"; }; }; virtualEnv = pythonSet.mkVirtualEnv "internal-policy-server-env" workspace.deps.default; in virtualEnv.overrideAttrs (oldAttrs: { meta = (oldAttrs.meta or {}) // { mainProgram = "internal-policy-server"; }; }); nftables-nologin-script = pkgs.resholve.writeScript "nftables-mail-nologin" { inputs = with pkgs; [inetutils nftables gnugrep findutils]; interpreter = lib.getExe pkgs.zsh; } '' set -e typeset -a as_sets mnt_bys route route6 as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets}) mnt_bys=(${lib.escapeShellArgs config.services.email.nologin.MNTBys}) for as_set in $as_sets; do while IFS=$'\n' read line; do if [[ "''${line}" =~ "^route:\s+(.+)$" ]]; then route+=($match[1]) elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then route6+=($match[1]) fi done < <(whois -h whois.radb.net "!i''${as_set},1" | grep -Eo 'AS[0-9]+' | xargs whois -h whois.radb.net -- -i origin) done for mnt_by in $mnt_bys; do while IFS=$'\n' read line; do if [[ "''${line}" =~ "^route:\s+(.+)$" ]]; then route+=($match[1]) elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then route6+=($match[1]) fi done < <(whois -h whois.radb.net "!o''${mnt_by}") done printf -v elements4 '%s,' "''${route[@]}" elements4=''${elements4%,} printf -v elements6 '%s,' "''${route6[@]}" elements6=''${elements6%,} nft -f - < Subject: Undelivered Mail Returned to Sender Postmaster-Subject: Postmaster Copy: Undelivered Mail This is the mail system at host $myhostname. I'm sorry to have to inform you that your message could not be delivered to one or more recipients. It's attached below. The mail system ''; delay_template_file = pkgs.writeText "delay.cf" '' Charset: us-ascii From: Mail Delivery System Subject: Delayed Mail (still being retried) Postmaster-Subject: Postmaster Warning: Delayed Mail This is the mail system at host $myhostname. #################################################################### # THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. # #################################################################### Your message could not be delivered for more than $delay_warning_time_minutes minute(s). It will be retried until it is $maximal_queue_lifetime_minutes minute(s) old. The mail system ''; 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 ''}"; virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" '' hosts = postgresql:///email dbname = email query = SELECT 1 FROM virtual_mailbox_domain WHERE domain = '%s' ''}''; virtual_mailbox_maps = ''pgsql:${pkgs.writeText "virtual_mailbox_maps.cf" '' hosts = postgresql:///email dbname = email query = SELECT 1 FROM virtual_mailbox_mapping WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_mapping WHERE lookup = '%s')) ''}''; dvlmtp_destination_recipient_limit = "1"; virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; smtputf8_enable = false; authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.user}= }"; authorized_flush_users = "inline:{ root= }"; authorized_mailq_users = "inline:{ root= }"; postscreen_access_list = ""; postscreen_denylist_action = "drop"; postscreen_greet_action = "enforce"; sender_bcc_maps = ''pgsql:${pkgs.writeText "sender_bcc_maps.cf" '' hosts = postgresql:///email dbname = email query = SELECT value FROM sender_bcc_maps WHERE key = '%s' ''}''; recipient_bcc_maps = ''pgsql:${pkgs.writeText "recipient_bcc_maps.cf" '' hosts = postgresql:///email dbname = email query = SELECT value FROM recipient_bcc_maps WHERE key = '%s' ''}''; }; settings.master = { "465" = { type = "inet"; private = false; command = "smtpd -v"; args = [ "-o" "smtpd_tls_security_level=encrypt" "-o" "{smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2}" "-o" "{smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, !TLSv1.2}" "-o" "smtpd_tls_mandatory_ciphers=high" "-o" "{tls_eecdh_auto_curves = X25519 X448}" "-o" "smtpd_tls_wrappermode=yes" "-o" "smtpd_tls_ask_ccert=yes" "-o" "smtpd_tls_req_ccert=yes" "-o" "smtpd_tls_received_header=no" "-o" "cleanup_service_name=subcleanup" "-o" "smtpd_client_restrictions=permit_tls_all_clientcerts,reject" "-o" "{smtpd_sender_restrictions = reject_unknown_sender_domain,reject_unverified_sender,check_policy_service unix:/run/postfix-ccert-sender-policy.sock}" "-o" ''{smtpd_recipient_restrictions=reject_unauth_pipelining,reject_non_fqdn_recipient,reject_unknown_recipient_domain,check_recipient_access pgsql:${pkgs.writeText "check_recipient_access.cf" '' hosts = postgresql:///email dbname = email 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')) ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_tls_all_clientcerts,reject}'' "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" "-o" "unverified_sender_reject_code=550" "-o" "unverified_sender_reject_reason={Sender address rejected: undeliverable address}" "-o" "milter_macro_daemon_name=surtr.yggdrasil.li" "-o" ''smtpd_milters=${config.services.opendkim.socket}'' ]; }; "466" = { type = "inet"; private = false; command = "smtpd -v"; args = [ "-o" "smtpd_tls_security_level=encrypt" "-o" "smtpd_tls_wrappermode=yes" "-o" "smtpd_tls_ask_ccert=no" "-o" "smtpd_tls_req_ccert=no" "-o" "smtpd_sasl_type=dovecot" "-o" "smtpd_sasl_path=/run/dovecot-sasl" "-o" "smtpd_sasl_auth_enable=yes" "-o" "smtpd_tls_received_header=no" "-o" "cleanup_service_name=subcleanup" "-o" "smtpd_client_restrictions=permit_sasl_authenticated,reject" "-o" "{smtpd_sender_restrictions = reject_unknown_sender_domain,reject_unverified_sender,check_policy_service unix:/run/postfix-ccert-sender-policy.sock}" "-o" ''{smtpd_recipient_restrictions=reject_unauth_pipelining,reject_non_fqdn_recipient,reject_unknown_recipient_domain,check_recipient_access pgsql:${pkgs.writeText "check_recipient_access.cf" '' hosts = postgresql:///email dbname = email 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')) ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_sasl_authenticated,reject}'' "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" "-o" "unverified_sender_reject_code=550" "-o" "unverified_sender_reject_reason={Sender address rejected: undeliverable address}" "-o" "milter_macro_daemon_name=surtr.yggdrasil.li" "-o" ''smtpd_milters=${config.services.opendkim.socket}'' ]; }; subcleanup = { command = "cleanup -v"; private = false; maxproc = 0; args = [ "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" '' if /^Received: / !/by surtr\.yggdrasil\.li/ STRIP /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 endif ''}" ]; }; dvlmtp = { command = "lmtp"; args = [ "flags=DORX" ]; }; smtp_pass = { name = "smtpd"; type = "pass"; command = "smtpd -v"; }; postscreen = { name = "smtp"; type = "inet"; private = false; command = "postscreen -v"; maxproc = 1; }; smtp = {}; relay = { command = "smtp"; args = [ "-o" "smtp_fallback_relay=" ]; }; tlsproxy = { maxproc = 0; }; dnsblog = {}; }; }; services.postsrsd = { enable = true; domains = [ "surtr.yggdrasil.li" ] ++ concatMap (domain: [".${domain}" domain]) emailDomains; separator = "+"; extraConfig = '' socketmap = unix:/run/postsrsd/postsrsd-socketmap.sock milter = unix:/run/postsrsd/postsrsd-milter.sock ''; }; services.opendkim = { enable = true; user = "postfix"; group = "postfix"; socket = "local:/run/opendkim/opendkim.sock"; domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li" "yggdrasil.li" "141.li" "kleen.li" "synapse.li" "praseodym.org"] ++ emailDomains)}''; 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 = { type = "controller"; count = 1; bindSockets = [ { mode = "0660"; socket = "/run/rspamd/worker-controller.sock"; owner = config.services.rspamd.user; group = config.services.rspamd.group; } ]; includes = []; extraConfig = '' static_dir = "''${WWWDIR}"; # Serve the web UI static assets ''; }; 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; timeout = 120s; 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 config.services.dovecot2.user ]; services.redis.servers.rspamd.enable = true; users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ]; environment.systemPackages = with pkgs; [ dovecot_pigeonhole dovecot_fts_xapian ]; services.dovecot2 = { enable = true; enablePAM = false; sslServerCert = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem"; sslServerKey = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem"; sslCACert = toString ./ca/ca.crt; mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; protocols = [ "lmtp" "sieve" ]; sieve = { extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"]; globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"]; }; extraConfig = let dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' driver = pgsql connect = dbname=email 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' user_query = SELECT "user", quota_rule, '${config.services.dovecot2.user}' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' iterate_query = SELECT "user" FROM imap_user ''; in '' mail_home = /var/lib/mail/%u mail_plugins = $mail_plugins quota first_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid} last_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid} first_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid} last_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid} ${concatMapStringsSep "\n\n" (domain: concatMapStringsSep "\n" (subdomain: '' local_name ${subdomain} { ssl_cert =