{ lib, pkgs, config, myconfig, ... }: { config.secrets.keys = [ { dest = "postfix/mysql_alias_maps"; user = config.services.postfix.user; group = config.services.postfix.group; permissions = "0440"; text = '' # We need to specify that option to trigger ssl connection tls_ciphers = TLSv1.2 user = ${myconfig.env.mail.postfix.mysql.user} password = ${myconfig.env.mail.postfix.mysql.password} hosts = unix:${myconfig.env.mail.postfix.mysql.socket} dbname = ${myconfig.env.mail.postfix.mysql.database} query = SELECT DISTINCT destination FROM forwardings_merge WHERE ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) AND active = 1 AND '%s' NOT IN ( SELECT source FROM forwardings_blacklisted WHERE source = '%s' ) UNION SELECT 'devnull@immae.eu' FROM forwardings_blacklisted WHERE source = '%s' ''; } { dest = "postfix/mysql_mailbox_maps"; user = config.services.postfix.user; group = config.services.postfix.group; permissions = "0440"; text = '' # We need to specify that option to trigger ssl connection tls_ciphers = TLSv1.2 user = ${myconfig.env.mail.postfix.mysql.user} password = ${myconfig.env.mail.postfix.mysql.password} hosts = unix:${myconfig.env.mail.postfix.mysql.socket} dbname = ${myconfig.env.mail.postfix.mysql.database} result_format = /%d/%u query = SELECT DISTINCT '%s' FROM mailboxes WHERE active = 1 AND ( (domain = '%d' AND user = '%u' AND regex = 0) OR ( regex = 1 AND '%d' REGEXP CONCAT('^',domain,'$') AND '%u' REGEXP CONCAT('^',user,'$') ) ) LIMIT 1 ''; } { dest = "postfix/mysql_sender_login_maps"; user = config.services.postfix.user; group = config.services.postfix.group; permissions = "0440"; text = '' # We need to specify that option to trigger ssl connection tls_ciphers = TLSv1.2 user = ${myconfig.env.mail.postfix.mysql.user} password = ${myconfig.env.mail.postfix.mysql.password} hosts = unix:${myconfig.env.mail.postfix.mysql.socket} dbname = ${myconfig.env.mail.postfix.mysql.database} query = SELECT DISTINCT destination FROM forwardings_merge WHERE ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) AND active = 1 ''; } ]; config.networking.firewall.allowedTCPPorts = [ 25 465 587 ]; config.nixpkgs.overlays = [ (self: super: { postfix = super.postfix.override { withMySQL = true; }; }) ]; config.users.users."${config.services.postfix.user}".extraGroups = [ "keys" ]; config.services.filesWatcher.postfix = { restart = true; paths = [ config.secrets.fullPaths."postfix/mysql_alias_maps" config.secrets.fullPaths."postfix/mysql_mailbox_maps" config.secrets.fullPaths."postfix/mysql_sender_login_maps" ]; }; config.services.postfix = { mapFiles = let recipient_maps = let name = n: i: "relay_${n}_${toString i}"; pair = n: i: m: lib.attrsets.nameValuePair (name n i) ( if m.type == "hash" then pkgs.writeText (name n i) m.content else null ); pairs = n: v: lib.imap1 (i: m: pair n i m) v.recipient_maps; in lib.attrsets.filterAttrs (k: v: v != null) ( lib.attrsets.listToAttrs (lib.flatten ( lib.attrsets.mapAttrsToList pairs myconfig.env.mail.postfix.backup_domains )) ); relay_restrictions = lib.attrsets.filterAttrs (k: v: v != null) ( lib.attrsets.mapAttrs' (n: v: lib.attrsets.nameValuePair "recipient_access_${n}" ( if lib.attrsets.hasAttr "relay_restrictions" v then pkgs.writeText "recipient_access_${n}" v.relay_restrictions else null ) ) myconfig.env.mail.postfix.backup_domains ); in recipient_maps // relay_restrictions; config = { ### postfix module overrides readme_directory = "${pkgs.postfix}/share/postfix/doc"; smtp_tls_CAfile = lib.mkForce ""; smtp_tls_cert_file = lib.mkForce ""; smtp_tls_key_file = lib.mkForce ""; message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited" alias_database = "\$alias_maps"; ### Virtual mailboxes config virtual_alias_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"; virtual_mailbox_domains = myconfig.env.mail.postfix.additional_mailbox_domains ++ lib.remove "localhost.immae.eu" (lib.remove null (lib.flatten (map (zone: map (e: if e.receive then "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}" else null ) (zone.withEmail or []) ) myconfig.env.dns.masterZones ))); virtual_mailbox_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_mailbox_maps"}"; dovecot_destination_recipient_limit = "1"; virtual_transport = "dovecot"; ### Relay domains relay_domains = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) myconfig.env.mail.postfix.backup_domains); relay_recipient_maps = lib.flatten (lib.attrsets.mapAttrsToList (n: v: lib.imap1 (i: m: "${m.type}:/etc/postfix/relay_${n}_${toString i}") v.recipient_maps ) myconfig.env.mail.postfix.backup_domains); smtpd_relay_restrictions = [ "permit_mynetworks" "permit_sasl_authenticated" "defer_unauth_destination" ] ++ lib.flatten (lib.attrsets.mapAttrsToList (n: v: if lib.attrsets.hasAttr "relay_restrictions" v then [ "check_recipient_access hash:/etc/postfix/recipient_access_${n}" ] else [] ) myconfig.env.mail.postfix.backup_domains); ### Additional smtpd configuration smtpd_tls_received_header = "yes"; smtpd_tls_loglevel = "1"; ### Email sending configuration smtp_tls_security_level = "may"; smtp_tls_loglevel = "1"; ### Force ip bind for smtp smtp_bind_address = myconfig.env.servers.eldiron.ips.main.ip4; smtp_bind_address6 = builtins.head myconfig.env.servers.eldiron.ips.main.ip6; # #Unneeded if postfix can only send e-mail from "self" domains # #smtp_sasl_auth_enable = "yes"; # #smtp_sasl_password_maps = "hash:/etc/postfix/relay_creds"; # #smtp_sasl_security_options = "noanonymous"; # #smtp_sender_dependent_authentication = "yes"; # #sender_dependent_relayhost_maps = "hash:/etc/postfix/sender_relay"; ### opendkim, opendmarc, openarc milters non_smtpd_milters = [ "unix:${config.myServices.mail.milters.sockets.opendkim}" "unix:${config.myServices.mail.milters.sockets.opendmarc}" "unix:${config.myServices.mail.milters.sockets.openarc}" ]; smtpd_milters = [ "unix:${config.myServices.mail.milters.sockets.opendkim}" "unix:${config.myServices.mail.milters.sockets.opendmarc}" "unix:${config.myServices.mail.milters.sockets.openarc}" ]; }; enable = true; enableSmtp = true; enableSubmission = true; submissionOptions = { smtpd_tls_security_level = "encrypt"; smtpd_sasl_auth_enable = "yes"; smtpd_tls_auth_only = "yes"; smtpd_sasl_tls_security_options = "noanonymous"; smtpd_sasl_type = "dovecot"; smtpd_sasl_path = "private/auth"; smtpd_reject_unlisted_recipient = "no"; smtpd_client_restrictions = "permit_sasl_authenticated,reject"; # Refuse to send e-mails with a From that is not handled smtpd_sender_restrictions = "reject_sender_login_mismatch,reject_unlisted_sender,permit_sasl_authenticated,reject"; smtpd_sender_login_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}"; smtpd_recipient_restrictions = "permit_sasl_authenticated,reject"; milter_macro_daemon_name = "ORIGINATING"; smtpd_milters = "unix:${config.myServices.mail.milters.sockets.opendkim}"; }; # FIXME: Mail adressed to localhost.immae.eu will still have mx-1 as # prioritized MX, which provokes "mail for localhost.immae.eu loops # back to myself" errors. This transport entry forces to push # e-mails to its right destination. transport = '' localhost.immae.eu smtp:[immae.eu]:25 ''; destination = ["localhost"]; # This needs to reverse DNS hostname = "eldiron.immae.eu"; setSendmail = true; sslCert = "/var/lib/acme/mail/fullchain.pem"; sslKey = "/var/lib/acme/mail/key.pem"; recipientDelimiter = "+"; masterConfig = { submissions = { type = "inet"; private = false; command = "smtpd"; args = ["-o" "smtpd_tls_wrappermode=yes" ] ++ (let mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ]; in lib.concatLists (lib.mapAttrsToList mkKeyVal config.services.postfix.submissionOptions) ); }; dovecot = { type = "unix"; privileged = true; chroot = false; command = "pipe"; args = let # rspamd could be used as a milter, but then it cannot apply # its checks "per user" (milter is not yet dispatched to # users), so we wrap dovecot-lda inside rspamc per recipient # here. dovecot_exe = "${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f \${sender} -a \${original_recipient} -d \${user}@\${nexthop}"; in [ "flags=DRhu" "user=vhost:vhost" "argv=${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d \${user}@\${nexthop} --mime --exec {${dovecot_exe}}" ]; }; }; }; config.security.acme.certs."mail" = { postRun = '' systemctl restart postfix.service ''; extraDomains = { "smtp.immae.eu" = null; }; }; }