X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=systems%2Feldiron%2Fmail%2Fpostfix.nix;fp=modules%2Fprivate%2Fmail%2Fpostfix.nix;h=f95ee1bb04217e6d9114fd8e04122a13433c3336;hb=1a64deeb894dc95e2645a75771732c6cc53a79ad;hp=ae98a8a8c82e0d5d0ba2f20b4eae4cc442a0ee93;hpb=fa25ffd4583cc362075cd5e1b4130f33306103f0;p=perso%2FImmae%2FConfig%2FNix.git diff --git a/modules/private/mail/postfix.nix b/systems/eldiron/mail/postfix.nix similarity index 71% rename from modules/private/mail/postfix.nix rename to systems/eldiron/mail/postfix.nix index ae98a8a..f95ee1b 100644 --- a/modules/private/mail/postfix.nix +++ b/systems/eldiron/mail/postfix.nix @@ -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; @@ -150,12 +206,7 @@ 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 ]; @@ -176,96 +227,52 @@ }; 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 <')" ''; - 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"; @@ -286,17 +293,7 @@ "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"}" ]; @@ -304,17 +301,9 @@ 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"; @@ -325,18 +314,23 @@ 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 = [ @@ -350,7 +344,10 @@ 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"; @@ -358,7 +355,8 @@ 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) @@ -369,19 +367,31 @@ 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" @@ -415,13 +425,20 @@ # 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" @@ -434,17 +451,13 @@ 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" ]; @@ -467,5 +480,18 @@ ''; }; 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"; + } + ]; }; }