]> git.immae.eu Git - perso/Immae/Config/Nix.git/blobdiff - systems/eldiron/mail/postfix.nix
Squash changes containing private information
[perso/Immae/Config/Nix.git] / systems / eldiron / mail / postfix.nix
similarity index 71%
rename from modules/private/mail/postfix.nix
rename to systems/eldiron/mail/postfix.nix
index ae98a8a8c82e0d5d0ba2f20b4eae4cc442a0ee93..f95ee1bb04217e6d9114fd8e04122a13433c3336 100644 (file)
@@ -1,6 +1,62 @@
-{ lib, pkgs, config, nodes, ... }:
+{ lib, pkgs, config, options, ... }:
+let
+  getDomains = p: lib.mapAttrsToList (n: v: v.fqdn) (lib.filterAttrs (n: v: v.receive) p.emailPolicies);
+  bydomain = builtins.mapAttrs (n: getDomains) config.myServices.dns.zones;
+  receiving_domains = lib.flatten (builtins.attrValues bydomain);
+in
 {
+  options.services.postfix.submissionOptions' = options.services.postfix.submissionOptions // {
+    type = with lib.types; attrsOf (either str (listOf str));
+    apply = builtins.mapAttrs (n: v: if builtins.isList v then builtins.concatStringsSep "," v else v);
+  };
   config = lib.mkIf config.myServices.mail.enable {
+    myServices.dns.zones."immae.eu" = with config.myServices.dns.helpers; lib.mkMerge [
+      mailMX
+      (mailCommon "immae.eu")
+      mailSend
+      {
+        # Virtual forwards and mailboxes for real users
+        emailPolicies."mail".receive = true;
+        # multi-domain generic mails:
+        #     hostmaster, cron, httpd, naemon, postmaster
+        # system virtual mailboxes:
+        #     devnull, printer, testconnect
+        emailPolicies."".receive = true;
+        subdomains.mail = lib.mkMerge [ (mailCommon "immae.eu") mailSend ];
+        subdomains.smtp = ips servers.eldiron.ips.main;
+
+        # DMARC reports
+        subdomains._dmarc.subdomains._report.subdomains = let
+          getDomains = p: lib.mapAttrsToList (n: v: v.fqdn) p.emailPolicies;
+          bydomain = builtins.mapAttrs (n: getDomains) config.myServices.dns.zones;
+          hostsWithMail = lib.flatten (builtins.attrValues bydomain);
+          nvpairs = builtins.map (e: { name = e; value = { TXT = [ "v=DMARC1;" ]; }; }) hostsWithMail;
+        in
+          builtins.listToAttrs nvpairs;
+      }
+    ];
+
+    myServices.chatonsProperties.hostings.mx-backup = {
+      file.datetime = "2022-08-22T01:00:00";
+      hosting = {
+        name = "MX Backup";
+        description = "Serveur e-mail secondaire";
+        logo = "https://www.postfix.org/favicon.ico";
+        website = "https://mail.immae.eu/";
+        status.level = "OK";
+        status.description = "OK";
+        registration.load = "OPEN";
+        install.type = "PACKAGE";
+      };
+      software = {
+        name = "Postfix";
+        website = "http://www.postfix.org/";
+        license.url = "http://postfix.mirrors.ovh.net/postfix-release/LICENSE";
+        license.name = "Eclipse Public license (EPL 2.0) and IBM Public License (IPL 1.0)";
+        version = pkgs.postfix.version;
+        source.url = "http://www.postfix.org/download.html";
+      };
+    };
     secrets.keys = {
       "postfix/mysql_alias_maps" = {
         user = config.services.postfix.user;
           version = 3
           '';
       };
-    } // lib.mapAttrs' (name: v: lib.nameValuePair "postfix/scripts/${name}-env" {
-      user = "postfixscripts";
-      group = "root";
-      permissions = "0400";
-      text = builtins.toJSON v.env;
-    }) config.myEnv.mail.scripts;
+    };
 
     networking.firewall.allowedTCPPorts = [ 25 465 587 ];
 
     };
     services.postfix = {
       extraAliases = let
-        toScript = name: script: pkgs.writeScript name ''
+        testmail = pkgs.writeScript "testmail" ''
           #! ${pkgs.stdenv.shell}
-          mail=$(${pkgs.coreutils}/bin/cat -)
-          output=$(echo "$mail" | ${script} 2>&1)
-          ret=$?
-
-          if [ "$ret" != "0" ]; then
-            echo "$mail" \
-              | ${pkgs.procmail}/bin/formail -i "X-Return-Code: $ret" \
-              | /run/wrappers/bin/sendmail -i scripts_error+${name}@mail.immae.eu
-
-          messageId=$(echo "$mail" | ${pkgs.procmail}/bin/formail -x "Message-Id:")
-          repeat=$(echo "$mail" | ${pkgs.procmail}/bin/formail -X "From:" -X "Received:")
-
-          ${pkgs.coreutils}/bin/cat <<EOF | /run/wrappers/bin/sendmail -i scripts_error+${name}@mail.immae.eu
-          $repeat
-          To: scripts_error+${name}@mail.immae.eu
-          Subject: Log from script error
-          Content-Type: text/plain; charset="UTF-8"
-          Content-Transfer-Encoding: 8bit
-          References:$messageId
-          MIME-Version: 1.0
-          X-Return-Code: $ret
-
-          Error code: $ret
-          Output of message:
-          --------------
-          $output
-          --------------
-          EOF
-          fi
+          ${pkgs.coreutils}/bin/touch \
+            "/var/lib/naemon/checks/email/$(${pkgs.procmail}/bin/formail -x To: | ${pkgs.coreutils}/bin/tr -d ' <>')"
           '';
-        scripts = lib.attrsets.mapAttrs (n: v:
-          toScript n (pkgs.callPackage (builtins.fetchGit { url = v.src.url; ref = "master"; rev = v.src.rev; }) { scriptEnv = config.secrets.fullPaths."postfix/scripts/${n}-env"; })
-        ) config.myEnv.mail.scripts // {
-          testmail = pkgs.writeScript "testmail" ''
-            #! ${pkgs.stdenv.shell}
-            ${pkgs.coreutils}/bin/touch \
-              "/var/lib/naemon/checks/email/$(${pkgs.procmail}/bin/formail -x To: | ${pkgs.coreutils}/bin/tr -d ' <>')"
-            '';
-        };
-      in builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (n: v: ''${n}: "|${v}"'') scripts);
+      in
+        ''testmail: "|${testmail}"'';
       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 = let
             cfg = config.myEnv.monitoring.email_check.eldiron;
             address = "${cfg.mail_address}@${cfg.mail_domain}";
+            aliases = config.myEnv.mail.postfix.common_aliases;
+            admins = builtins.concatStringsSep "," config.myEnv.mail.postfix.admins;
           in pkgs.writeText "postfix-virtual" (
             builtins.concatStringsSep "\n" (
-              ["${address} testmail@localhost"] ++
-              lib.attrsets.mapAttrsToList (
-                n: v: lib.optionalString v.external ''
-                  script_${n}@mail.immae.eu ${n}@localhost, scripts@mail.immae.eu
-                ''
-              ) config.myEnv.mail.scripts
-            )
-          );
+              [ "${address} testmail@localhost"
+              ] ++
+              map (a: "${a} ${admins}") config.myEnv.mail.postfix.other_aliases
+              ++ lib.lists.flatten (
+                map (domain:
+                  map (alias: "${alias}@${domain} ${admins}") aliases
+                ) receiving_domains
+                )
+          ));
         };
         sasl_access = {
           host_sender_login = with lib.attrsets; let
             addresses = zipAttrs (lib.flatten (mapAttrsToList
               (n: v: (map (e: { "${e}" = "${n}@immae.eu"; }) v.emails)) config.myEnv.servers));
+            aliases = config.myEnv.mail.postfix.common_aliases;
             joined = builtins.concatStringsSep ",";
+            admins = joined config.myEnv.mail.postfix.admins;
           in pkgs.writeText "host-sender-login"
-            (builtins.concatStringsSep "\n" (mapAttrsToList (n: v: "${n} ${joined v}") addresses));
+            (builtins.concatStringsSep "\n" (
+              mapAttrsToList (n: v: "${n} ${joined v}") addresses
+              ++ lib.lists.flatten (
+                map (domain:
+                  map (alias: "${alias}@${domain} ${admins}") aliases
+                ) receiving_domains
+                )
+              ++ map (a: "${a} ${admins}") config.myEnv.mail.postfix.other_aliases
+          ));
         };
       in
-        recipient_maps // relay_restrictions // virtual_map // sasl_access;
+        virtual_map // sasl_access;
       config = {
         ### postfix module overrides
         readme_directory = "${pkgs.postfix}/share/postfix/doc";
           "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"
           "ldap:${config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"}"
         ];
-        virtual_mailbox_domains = config.myEnv.mail.postfix.additional_mailbox_domains
-        ++ 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
-          ));
+        virtual_mailbox_domains = receiving_domains;
         virtual_mailbox_maps = [
           "ldap:${config.secrets.fullPaths."postfix/ldap_mailboxes"}"
         ];
         virtual_transport = "dovecot";
 
         ### Relay domains
-        relay_domains = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) config.myEnv.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
-        ) config.myEnv.mail.postfix.backup_domains);
         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";
         smtp_tls_loglevel = "1";
 
         ### Force ip bind for smtp
-        smtp_bind_address  = config.hostEnv.ips.main.ip4;
+        smtp_bind_address  = builtins.head config.hostEnv.ips.main.ip4;
         smtp_bind_address6 = builtins.head config.hostEnv.ips.main.ip6;
 
         # Use some relays when authorized senders are not myself
-        smtp_sasl_mechanism_filter = "plain,login"; # GSSAPI Not correctly supported by postfix
+        smtp_sasl_mechanism_filter = [
+          "plain"
+          "login"
+        ]; # GSSAPI Not correctly supported by postfix
         smtp_sasl_auth_enable = "yes";
-        smtp_sasl_password_maps =
-          "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_creds"}";
+        smtp_sasl_password_maps = [
+          "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_creds"}"
+        ];
         smtp_sasl_security_options = "noanonymous";
         smtp_sender_dependent_authentication = "yes";
-        sender_dependent_relayhost_maps =
-          "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_hosts"}";
+        sender_dependent_relayhost_maps = [
+          "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_hosts"}"
+        ];
 
         ### opendkim, opendmarc, openarc milters
         non_smtpd_milters = [
 
         smtp_use_tls = true;
         smtpd_use_tls = true;
-        smtpd_tls_chain_files = builtins.concatStringsSep "," [ "/var/lib/acme/mail/full.pem" "/var/lib/acme/mail-rsa/full.pem" ];
+        smtpd_tls_chain_files = [
+          "/var/lib/acme/mail/full.pem"
+          "/var/lib/acme/mail-rsa/full.pem"
+        ];
 
         maximal_queue_lifetime = "6w";
         bounce_queue_lifetime = "6w";
       enable = true;
       enableSmtp = true;
       enableSubmission = true;
-      submissionOptions = {
+      submissionOptions = config.services.postfix.submissionOptions';
+      submissionOptions' = {
         # Don’t use "long form", only commas (cf
         # http://www.postfix.org/master.5.html long form is not handled
         # well by the submission function)
         smtpd_sasl_type = "dovecot";
         smtpd_sasl_path = "private/auth";
         smtpd_reject_unlisted_recipient = "no";
-        smtpd_client_restrictions = "permit_sasl_authenticated,reject";
-        smtpd_relay_restrictions = "permit_sasl_authenticated,reject";
+        smtpd_client_restrictions = [
+          "permit_sasl_authenticated"
+          "reject"
+        ];
+        smtpd_relay_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 = builtins.concatStringsSep "," [
+        smtpd_sender_restrictions = [
+          "reject_sender_login_mismatch"
+          "reject_unlisted_sender"
+          "permit_sasl_authenticated,reject"
+        ];
+        smtpd_sender_login_maps = [
           "hash:/etc/postfix/host_sender_login"
           "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_maps"}"
           "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}"
         ];
-        smtpd_recipient_restrictions = "permit_sasl_authenticated,reject";
+        smtpd_recipient_restrictions = [
+          "permit_sasl_authenticated"
+          "reject"
+        ];
         milter_macro_daemon_name = "ORIGINATING";
-        smtpd_milters = builtins.concatStringsSep "," [
+        smtpd_milters = [
           # FIXME: put it back when opensmtpd is upgraded and able to
           # rewrite the from header
           #"unix:/run/milter_verify_from/verify_from.sock"
             # here.
             rspamc_dovecot = pkgs.writeScriptBin "rspamc_dovecot" ''
               #! ${pkgs.stdenv.shell}
+              set -o pipefail
               sender="$1"
               original_recipient="$2"
               user="$3"
 
               ${pkgs.coreutils}/bin/cat - | \
-                (${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d "$user" --mime || true) | \
+                ${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d "$user" --mime | \
                 ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f "$sender" -a "$original_recipient" -d "$user"
+              if echo ''${PIPESTATUS[@]} | ${pkgs.gnugrep}/bin/grep -qE '^[0 ]+$'; then
+                exit 0
+              else
+                # src/global/sys_exits.h to retry
+                exit 75
+              fi
               '';
           in [
             "flags=ODRhu" "user=vhost:vhost"
       postRun = ''
         systemctl restart postfix.service
         '';
-      extraDomains = {
-        "smtp.immae.eu" = null;
-      };
+      extraDomainNames = [ "smtp.immae.eu" ];
     };
     security.acme.certs."mail-rsa" = {
       postRun = ''
         systemctl restart postfix.service
         '';
-      extraDomains = {
-        "smtp.immae.eu" = null;
-      };
+      extraDomainNames = [ "smtp.immae.eu" ];
     };
     system.activationScripts.testmail = {
       deps = [ "users" ];
         '';
     };
     systemd.services.postfix.serviceConfig.Slice = "mail.slice";
+
+    myServices.monitoring.fromMasterObjects.service = [
+      {
+        service_description = "postfix SSL is up to date";
+        host_name = config.hostEnv.fqdn;
+        use = "external-service";
+        check_command = "check_smtp";
+
+        servicegroups = "webstatus-ssl";
+        _webstatus_name = "SMTP";
+        _webstatus_url = "smtp.immae.eu";
+      }
+    ];
   };
 }