+{ lib, pkgs, config, nodes, name, ... }:
+{
+ config = lib.mkIf config.myServices.mailBackup.enable {
+ security.acme.certs."mail" = config.myServices.certificates.certConfig // {
+ postRun = ''
+ systemctl restart postfix.service
+ '';
+ domain = config.hostEnv.fqdn;
+ extraDomains = let
+ zonesWithMx = builtins.filter (zone:
+ lib.attrsets.hasAttr "withEmail" zone && lib.lists.length zone.withEmail > 0
+ ) config.myEnv.dns.masterZones;
+ mxs = map (zone: "${config.myEnv.servers."${name}".mx.subdomain}.${zone.name}") zonesWithMx;
+ in builtins.listToAttrs (map (mx: lib.attrsets.nameValuePair mx null) mxs);
+ };
+ 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 = ${config.myEnv.mail.postfix.mysql.user}
+ password = ${config.myEnv.mail.postfix.mysql.password}
+ hosts = ${config.myEnv.mail.postfix.mysql.remoteHost}
+ dbname = ${config.myEnv.mail.postfix.mysql.database}
+ query = SELECT DISTINCT 1
+ 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 = ${config.myEnv.mail.postfix.mysql.user}
+ password = ${config.myEnv.mail.postfix.mysql.password}
+ hosts = ${config.myEnv.mail.postfix.mysql.remoteHost}
+ dbname = ${config.myEnv.mail.postfix.mysql.database}
+ query = SELECT DISTINCT 1
+ 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/ldap_ejabberd_users_immae_fr";
+ user = config.services.postfix.user;
+ group = config.services.postfix.group;
+ permissions = "0440";
+ text = ''
+ server_host = ldaps://${config.myEnv.jabber.ldap.host}:636
+ search_base = ${config.myEnv.jabber.ldap.base}
+ query_filter = ${config.myEnv.jabber.postfix_user_filter}
+ domain = immae.fr
+ bind_dn = ${config.myEnv.jabber.ldap.dn}
+ bind_pw = ${config.myEnv.jabber.ldap.password}
+ result_attribute = immaeXmppUid
+ result_format = ejabberd@localhost
+ version = 3
+ '';
+ }
+ ];
+
+ networking.firewall.allowedTCPPorts = [ 25 ];
+
+ nixpkgs.overlays = [ (self: super: {
+ postfix = super.postfix.override { withMySQL = true; };
+ }) ];
+ users.users."${config.services.postfix.user}".extraGroups = [ "keys" ];
+ services.filesWatcher.postfix = {
+ restart = true;
+ paths = [
+ config.secrets.fullPaths."postfix/mysql_alias_maps"
+ config.secrets.fullPaths."postfix/mysql_mailbox_maps"
+ config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"
+ ];
+ };
+ 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 config.myEnv.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
+ )
+ ) config.myEnv.mail.postfix.backup_domains
+ );
+ virtual_map = {
+ virtual = pkgs.writeText "postfix-virtual" (
+ builtins.concatStringsSep "\n" (
+ lib.attrsets.mapAttrsToList (
+ n: v: lib.optionalString v.external ''
+ script_${n}@mail.immae.eu 1
+ ''
+ ) config.myEnv.mail.scripts
+ )
+ );
+ };
+ sasl_access = {
+ host_dummy_mailboxes = pkgs.writeText "host-virtual-mailbox"
+ (builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (n: v: "${n}@immae.eu 1") nodes));
+ };
+ in
+ recipient_maps // relay_restrictions // virtual_map // sasl_access;
+ 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"
+ mailbox_size_limit = "1073741825"; # Workaround, local delivered mails should all go through scripts
+ alias_database = "\$alias_maps";
+
+ ### Relay domains
+ relay_domains = let
+ backups = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) config.myEnv.mail.postfix.backup_domains);
+ virtual_domains = config.myEnv.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 [])
+ )
+ config.myEnv.dns.masterZones
+ )));
+ in
+ backups ++ virtual_domains;
+ relay_recipient_maps = let
+ backup_recipients = lib.flatten (lib.attrsets.mapAttrsToList (n: v:
+ lib.imap1 (i: m: "${m.type}:/etc/postfix/relay_${n}_${toString i}") v.recipient_maps
+ ) config.myEnv.mail.postfix.backup_domains);
+ virtual_alias_maps = [
+ "hash:/etc/postfix/virtual"
+ "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"
+ "ldap:${config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"}"
+ ];
+ virtual_mailbox_maps = [
+ "hash:/etc/postfix/host_dummy_mailboxes"
+ "mysql:${config.secrets.fullPaths."postfix/mysql_mailbox_maps"}"
+ ];
+ in
+ backup_recipients ++ virtual_alias_maps ++ virtual_mailbox_maps;
+ smtpd_relay_restrictions = [
+ "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 []
+ ) config.myEnv.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 = config.myEnv.servers."${name}".ips.main.ip4;
+ smtp_bind_address6 = builtins.head config.myEnv.servers."${name}".ips.main.ip6;
+
+ smtpd_milters = [
+ "unix:${config.myServices.mail.milters.sockets.opendkim}"
+ "unix:${config.myServices.mail.milters.sockets.openarc}"
+ "unix:${config.myServices.mail.milters.sockets.opendmarc}"
+ ];
+ };
+ enable = true;
+ enableSmtp = true;
+ enableSubmission = false;
+ # 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 = config.hostEnv.fqdn;
+ setSendmail = false;
+ sslCert = "/var/lib/acme/mail/fullchain.pem";
+ sslKey = "/var/lib/acme/mail/key.pem";
+ recipientDelimiter = "+";
+ };
+ };
+}
+