-{ 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";
+ }
+ ];
};
}