1 {pkgs, lib, config, nodes, ...}:
 
   2 let inherit (builtins) attrNames toFile;
 
   4     inherit (pkgs.lib) unlines unlinesAttrs;
 
   5     inherit (config.services) x509 postfix dovecot2 postgrey openldap;
 
   6     unwords = lib.concatStringsSep " ";
 
   7     when    = x: y: if x == null then "" else y;
 
   9     submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" ''
 
  10       # Removes sensitive headers from mails handed in via the submission or smtps port.
 
  11       # See https://thomas-leister.de/mailserver-debian-stretch/
 
  12       # Uses "pcre" style regex.
 
  18       /^X-Originating-IP:/ IGNORE
 
  22 options.services.postfix.aliases = lib.mkOption {
 
  23   type    = with types; attrsOf (listOf str);
 
  25   example = { "root@${config.networking.domain}" = [
 
  26                 "user1@${config.networking.domain}"
 
  27                 "user2@${config.networking.domain}"
 
  29               "@example.coop" = ["user1@${config.networking.domain}"];
 
  33   systemd.services.postfix.after =
 
  35     ] ++ (if x509.scheme == "letsencrypt"
 
  36           then [ "nginx.service" ] # XXX: not sure if this is enough
 
  40     #hostname = config.networking.domain;
 
  41     #domain = "localdomain";
 
  42     networksStyle = "host";
 
  43     #mapFiles."valias" = toFile "valias" (unlines (all_valiases_postfix ++ catchAllPostfix));
 
  44     # See https://blog.grimneko.de/2011/12/24/a-bunch-of-tips-for-improving-your-postfix-setup/
 
  45     # for details on how this file looks. By using the same file as valias,
 
  46     # every alias is uniquely owned by its user.
 
  47     # The user's own address is already in all_valiases_postfix.
 
  48     #mapFiles."vaccounts" = toFile "vaccounts" (unlines all_valiases_postfix);
 
  49     mapFiles."virtual_alias_maps" =
 
  50       toFile "virtual_alias_maps"
 
  52          (from: to: "${from} ${unwords to}")
 
  54     mapFiles."ldap-virtual_alias_maps.cf" =
 
  55       toFile "ldap-virtual_alias_maps.cf" ''
 
  58         server_host      = ldapi://
 
  61         search_base      = ou=posix,${openldap.domainSuffix}
 
  64         query_filter     = (&(mailAlias=%s)(mailEnabled=TRUE))
 
  66         result_attribute = mail
 
  68     mapFiles."ldap-forward.cf" =
 
  69       toFile "ldap-forward.cf" ''
 
  72         server_host      = ldapi://
 
  75         search_base      = ou=posix,${openldap.domainSuffix}
 
  78         query_filter     = (&(mail=%s)(mailEnabled=TRUE))
 
  80         result_attribute = mailForwardingAddress
 
  84     #enableSubmission = true;
 
  88       "localhost.localdomain"
 
  89       config.networking.hostName
 
  90       "${config.networking.hostName}.localdomain"
 
  96     recipientDelimiter = "+";
 
  98       # Appending .domain is the MUA's job
 
  99       append_dot_mydomain = false;
 
 100       # No console bell on new mail
 
 103       #content_filter = "amavisfeed:[127.0.0.1]:10024";
 
 104       #debug_peer_level = 4;
 
 105       #debug_peer_list = ".$myhostname";
 
 106       default_extra_recipient_limit = "5000";
 
 107       # Uncomment the next line to generate "delayed mail" warnings
 
 108       #delay_warning_time = "4h";
 
 109       # Stops some techniques used to harvest email addresses
 
 110       disable_vrfy_command = true;
 
 111       duplicate_filter_limit = "5000";
 
 112       enable_long_queue_ids = false;
 
 113       # Pass unexisting $mydestination recipients to dovecot
 
 114       fallback_transport = "lmtp:unix:private/dovecot-lmtp";
 
 116         ''$home/.forward''${recipient_delimiter}''${extension}''
 
 119       #header_checks = "regexp:/var/lib/postfix/conf/header_checks";
 
 120       #inet_interfaces = "all";
 
 121       line_length_limit = "2048";
 
 123       # Let $fallback_transport check existence of recipients
 
 124       local_recipient_maps = "";
 
 125       #mail_spool_directory = "/var/spool/mail";
 
 126         # NOTE: nixpkgs's default
 
 127       #local_header_rewrite_clients = "";
 
 128       #home_mailbox = "Maildir/";
 
 129       #mailbox_command = ''
 
 130       #  ${pkgs.procmail}/bin/procmail -t -a "$SENDER" -a "$RECIPIENT" -a "$USER" -a "$EXTENSION" -a "$DOMAIN" -a "$ORIGINAL_RECIPIENT" "$HOME/.procmailrc"
 
 132       mailbox_size_limit = "204800000";
 
 134       masquerade_classes = [ "envelope_sender" "header_sender" "header_recipient" ];
 
 135       masquerade_domains = "";
 
 136       masquerade_exceptions = "root";
 
 137       maximal_queue_lifetime = "5d";
 
 138       message_size_limit = "20480000";
 
 139       mime_header_checks = "";
 
 140       milter_header_checks = "";
 
 141       nested_header_checks = "";
 
 142       non_smtpd_milters = "";
 
 143       parent_domain_matches_subdomains = [
 
 145         #"fast_flush_domains"
 
 147         #"permit_mx_backup_networks"
 
 148         #"qmqpd_authorized_clients"
 
 151       permit_mx_backup_networks = "";
 
 152       #policy-spf_time_limit = "3600s";
 
 153       propagate_unmatched_extensions = [ "canonical" "virtual" "alias" ];
 
 155       #receive_override_options = "no_address_mappings";
 
 156         # no_unknown_recipient_checks
 
 157         #         Do not try to reject unknown recipients (SMTP server only).
 
 158         #         This is typically specified AFTER an external content filter.
 
 159         # no_address_mappings
 
 160         #         Disable canonical address mapping, virtual alias map expansion,
 
 161         #         address masquerading, and automatic BCC (blind carbon-copy) recipients.
 
 162         #         This is typically specified BEFORE an external content filter (eg. amavis).
 
 163         # no_header_body_checks
 
 164         #         Disable header/body_checks. This is typically specified AFTER
 
 165         #         an external content filter.
 
 167         #         Disable Milter (mail filter) applications.
 
 168         #         This is typically specified AFTER an external content filter.
 
 169       # Parse the extension in email address, eg. contact+extension@
 
 171       #relay_clientcerts = hash:/var/lib/postfix/conf/relay_clientcerts
 
 172       # This is where to put backup MX domains
 
 173       relay_domains = "$mydestination";
 
 174       relay_recipient_maps = "";
 
 175       smtp_body_checks = "";
 
 176       #smtp_cname_overrides_servername = false;
 
 177       smtp_connect_timeout = "60s";
 
 178       #smtp_header_checks = "regexp:/var/lib/postfix/smtp_header_checks";
 
 179       smtp_mime_header_checks = "";
 
 180       smtp_nested_header_checks = "";
 
 181       smtp_tls_exclude_ciphers = [ "ADH" "MD5" "CAMELLIA" "SEED" "3DES" "DES" "RC4" "eNULL" "aNULL" ];
 
 182       #smtp_tls_fingerprint_digest = "sha1";
 
 183       smtp_tls_loglevel = "1";
 
 184       #smtp_tls_note_starttls_offer = true;
 
 185       #smtp_tls_policy_maps = "hash:/var/lib/postfix/conf/tls_policy";
 
 186       # Only allow TLSv* protocols
 
 187       smtp_tls_protocols = [ "!SSLv2" "!SSLv3" ];
 
 188       smtp_tls_scert_verifydepth = "5";
 
 189       #smtp_tls_secure_cert_match = [ "nexthop" "dot-nexthop" ];
 
 190       smtp_tls_security_level = "may";
 
 191       smtp_tls_session_cache_database = "btree:$data_directory/smtp_tls_session_cache";
 
 192       #smtp_tls_session_cache_timeout = "3600s";
 
 193       #smtp_tls_verify_cert_match = "hostname";
 
 194       # Useful to test restrictions
 
 195       smtpd_authorized_xclient_hosts = "127.0.0.1";
 
 196       smtpd_banner = "${config.networking.fqdn} ESMTP $mail_name (NixOS)";
 
 197       smtpd_client_connection_count_limit = "50";
 
 198       smtpd_client_connection_rate_limit = "0";
 
 199       smtpd_client_event_limit_exceptions = "$mynetworks";
 
 200       smtpd_client_message_rate_limit = "0";
 
 201       smtpd_client_new_tls_session_rate_limit = "0";
 
 202       smtpd_client_port_logging = false;
 
 203       smtpd_client_recipient_rate_limit = "0";
 
 204       smtpd_client_restrictions = [
 
 205         #"check_client_access hash:/var/lib/postfix/conf/client_blacklist"
 
 207       smtpd_data_restrictions = [
 
 208         "reject_unauth_pipelining"
 
 209           # Force the smtp client to wait OK before sending
 
 212       # Disable opportunistic encryption
 
 213       smtpd_discard_ehlo_keywords = "starttls";
 
 214       #smtpd_end_of_data_restrictions = "";
 
 216       smtpd_error_sleep_time = "5";
 
 217       smtpd_helo_required = true;
 
 218       smtpd_helo_restrictions = [
 
 219         "reject_invalid_helo_hostname"
 
 220         "reject_non_fqdn_helo_hostname"
 
 221         #"reject_unknown_helo_hostname"
 
 222           # May be useful to fight spam
 
 227       smtpd_peername_lookup = true;
 
 228       smtpd_recipient_limit = "5000";
 
 229       smtpd_recipient_overshoot_limit = "5000";
 
 230       smtpd_recipient_restrictions = [
 
 231         "reject_non_fqdn_recipient"
 
 232         #"reject_invalid_hostname"
 
 233         "reject_unknown_recipient_domain"
 
 234         #"reject_non_fqdn_sender"
 
 235         "reject_unauth_pipelining"
 
 236         #"check_policy_service inet:localhost:12340"
 
 239         "permit_tls_clientcerts"
 
 240         "permit_sasl_authenticated"
 
 241         "reject_unverified_recipient"
 
 242           # $fallback_transport is responsible of checking the existence of the recipient
 
 243           # WARNING: verify(8) has a cache, dumpable if verify(8) is stopped, with:
 
 244           # postmap -s btree:/var/lib/postfix/data/verify_cache
 
 245         # Bypass SPF check and postgrey if the recipient is not for us or someone in backup_mx
 
 246         "reject_unauth_destination"
 
 248         #"check_policy_service unix:private/spfcheck"
 
 249         # Greylisting using postgrey
 
 250         "check_policy_service unix:${postgrey.socket.path}"
 
 251         # Once postgrey passed, permit what is for us
 
 252         "permit_auth_destination"
 
 254         #"reject_unknown_sender_domain"
 
 255           # Maybe better in smtpd_sender_restrictions
 
 256         #"reject_rbl_client bl.spamcop.net"
 
 257         #"reject_rbl_client list.dsbl.org"
 
 258         #"reject_rbl_client zen.spamhaus.org"
 
 259         #"reject_rbl_client dnsbl.sorbs.net"
 
 261       smtpd_relay_restrictions = [
 
 263         "permit_sasl_authenticated"
 
 264           # NOTE: permit auth through dovecot's SASL
 
 265         "reject_unauth_destination"
 
 267       #smtpd_restriction_classes = "";
 
 268       broken_sasl_auth_clients = false;
 
 269       #smtpd_sasl_auth_enable = true;
 
 270       #smtpd_sasl_path = "private/auth";
 
 271       #smtpd_sasl_security_options = "noanonymous";
 
 272       #smtpd_sasl_type = "dovecot";
 
 273       smtpd_sender_restrictions = [
 
 275         "permit_tls_clientcerts"
 
 276         "permit_sasl_authenticated"
 
 277           # NOTE: permit auth through dovecot's SASL
 
 278         #"check_sender_access hash:/var/lib/postfix/conf/sender_access"
 
 279         "reject_unauth_pipelining"
 
 280         "reject_non_fqdn_sender"
 
 281         #"reject_sender_login_mismatch"
 
 282         #"reject_unknown_sender_domain"
 
 285       smtpd_starttls_timeout = "300s";
 
 286       #smtpd_tls_always_issue_session_ids = true;
 
 287       # No SASL AUTH without TLS
 
 288       smtpd_tls_auth_only = true;
 
 289       #smtpd_tls_CApath = "/etc/postfix/x509/ca/";
 
 290       smtpd_tls_ask_ccert = false;
 
 291       #smtpd_tls_ccert_verifydepth = "5";
 
 292       smtpd_tls_ciphers = "high";
 
 293       smtpd_tls_eecdh_grade = "ultra";
 
 294       # Disable weak ciphers as reported by https://ssl-tools.net
 
 295       # https://serverfault.com/questions/744168/how-to-disable-rc4-on-postfix
 
 296       smtpd_tls_exclude_ciphers = [ "RC4" "aNULL" ];
 
 297       smtpd_tls_fingerprint_digest = "sha512";
 
 298       # Log only a summary message on TLS handshake completion
 
 299       smtpd_tls_loglevel = "1";
 
 300       smtpd_tls_mandatory_ciphers = "high";
 
 301       smtpd_tls_mandatory_protocols = "TLSv1";
 
 303       smtpd_tls_protocols = [ "!SSLv2" "!SSLv3" ];
 
 304       #smtpd_tls_received_header = false;
 
 305       smtpd_tls_req_ccert = false;
 
 306       # Postfix 2.3 and later
 
 308       #  Mandatory TLS encryption: announce STARTTLS support to SMTP clients, and require that clients use TLS
 
 309       #  encryption. According to [1720]RFC 2487 this MUST NOT be applied in case of a publicly-referenced
 
 310       #  SMTP server. Instead, this option should be used only on dedicated servers.
 
 311       smtpd_tls_security_level = "may";
 
 312       smtpd_tls_session_cache_database = "btree:$data_directory/smtpd_tls_session_cache";
 
 313       #smtpd_tls_session_cache_timeout = "3600s";
 
 314       # Stops mail from poorly written software
 
 315       strict_rfc821_envelopes = true;
 
 316       #sympa_destination_recipient_limit = "1";
 
 317       #sympabounce_destination_recipient_limit = "1";
 
 318       # postconf(5) discourages to change this
 
 319       #tls_high_cipherlist = "AES256-SHA";
 
 320       #tls_random_bytes = "32";
 
 321       # Must not be in a chroot
 
 322       #tls_random_exchange_name = "$data_directory/prng_exch";
 
 323       #tls_random_prng_update_period = "3600s";
 
 324       #tls_random_reseed_period = "3600s";
 
 325       # Use a non blocking source of randomness
 
 326       tls_random_source = "dev:/dev/urandom";
 
 329         #"hash:/etc/postfix/transport-dovecot"
 
 330         #"hash:/etc/postfix/$mydomain/transport"
 
 331         #"hash:/etc/dovecot/transport"
 
 332         #"regexp:/etc/sympa/transport"
 
 334       # Rejects immediately what $fallback_transport rejects
 
 335       unverified_recipient_reject_code = "550";
 
 336       # Do not specify virtual alias domain names in mydestination
 
 337       # or relay_domains configuration parameters
 
 339       # With  a  virtual  alias  domain,  the  Postfix SMTP server
 
 340       # accepts  mail  for  known-user@virtual-alias.domain,   and
 
 341       # rejects   mail  for  unknown-user@virtual-alias.domain  as
 
 343       virtual_alias_domains = [];
 
 344       virtual_alias_maps = [
 
 345         #"hash:/etc/postfix/virtual_alias_maps"
 
 346         "ldap:/etc/postfix/ldap-forward.cf"
 
 347         "ldap:/etc/postfix/ldap-virtual_alias_maps.cf"
 
 348         #"hash:/etc/postfix/virtual_alias-dovecot"
 
 349         #"hash:/var/lib/postfix/conf/valias"
 
 350         #"regexp:/etc/sympa/virtual_alias"
 
 352       #virtual_uid_maps = "static:5000";
 
 353       #virtual_gid_maps = "static:5000";
 
 354       #virtual_mailbox_base = dovecot2.mailDir;
 
 355       virtual_mailbox_domains = toFile "virtual_mailbox_domains" (unlines (attrNames dovecot2.domains));
 
 356       #virtual_mailbox_maps = "hash:/etc/postfix/virtual_mailbox_maps";
 
 357       virtual_transport = "lmtp:unix:private/dovecot-lmtp";
 
 359     #submissionOptions = {
 
 360     #  smtpd_tls_security_level     = "encrypt";
 
 361     #  smtpd_sasl_auth_enable       = "yes";
 
 362     #  smtpd_sasl_type              = "dovecot";
 
 363     #  smtpd_sasl_path              = "private/auth";
 
 364     #  smtpd_sasl_security_options  = "noanonymous";
 
 365     #  smtpd_sasl_local_domain      = "$myhostname";
 
 366     #  smtpd_client_restrictions    = "permit_sasl_authenticated,reject";
 
 367     #  smtpd_sender_login_maps      = "hash:/etc/postfix/vaccounts";
 
 368     #  smtpd_sender_restrictions    = "reject_sender_login_mismatch";
 
 369     #  smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
 
 370     #  cleanup_service_name         = "submission-header-cleanup";
 
 373       #spfcheck    unix  -        n       n       -        0        spawn
 
 374       #  user=policyd-spf argv=/usr/sbin/postfix-policyd-spf-perl
 
 375       465         inet  n        -       -       -        -        smtpd
 
 376         -o milter_macro_daemon_name=ORIGINATING
 
 377         -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 
 378         -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
 
 379         -o smtpd_sasl_auth_enable=yes
 
 380         -o smtpd_sasl_local_domain=$myhostname
 
 381         -o smtpd_sasl_path=private/auth
 
 382         -o smtpd_sasl_security_options=noanonymous
 
 383         -o smtpd_sasl_type=dovecot
 
 384         -o smtpd_tls_ask_ccert=no
 
 385         -o smtpd_tls_auth_only=yes
 
 386         -o smtpd_tls_ccert_verifydepth=0
 
 387         -o smtpd_tls_loglevel=1
 
 388         -o smtpd_tls_req_ccert=no
 
 389         -o smtpd_tls_security_level=encrypt
 
 390         -o smtpd_tls_wrappermode=yes
 
 391       # -o smtpd_sender_restrictions=reject_sender_login_mismatch
 
 392       # -o smtpd_sender_login_maps=hash:/etc/postfix/vaccounts
 
 393       # -o cleanup_service_name=submission-header-cleanup
 
 394       submission-header-cleanup unix n - n    -       0       cleanup
 
 395         -o header_checks=pcre:${submissionHeaderCleanupRules}
 
 396       #spfcheck  unix  -       n       n       -       0       spawn
 
 397       #  user=policyd-spf argv=/usr/bin/postfix-policyd-spf-perl
 
 398       #uucp      unix  -       n       n       -       -       pipe
 
 399       #  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
 
 400       #smtp      inet  n       -       -       -       -       smtpd
 
 401       #  -o cleanup_service_name=pre-cleanup
 
 402       #  -o content_filter=amavis:[127.0.0.1]:10024
 
 403       #  -o smtpd_sender_restrictions=reject_unauth_pipelining,reject_non_fqdn_sender,permit
 
 404       #  -o receive_override_options=no_address_mappings
 
 405       #amavis    unix  -       -       n        -      2       lmtp
 
 406       #  -o lmtp_data_done_timeout=1200
 
 407       #  -o lmtp_send_xforward_command=yes
 
 408       #  -o lmtp_tls_note_starttls_offer=no
 
 409       #127.0.0.1:10025 inet n  -       n       -       -       smtpd
 
 411       #  -o local_header_rewrite_clients=
 
 412       #  -o local_recipient_maps=
 
 413       #  -o mynetworks=127.0.0.0/8
 
 414       #  -o receive_override_options=no_header_body_checks,no_milters,no_unknown_recipient_checks
 
 415       #  -o relay_recipient_maps=
 
 416       #  -o smtpd_client_connection_count_limit=0
 
 417       #  -o smtpd_client_connection_rate_limit=0
 
 418       #  -o smtpd_client_restrictions=permit_mynetworks,reject
 
 419       #  -o smtpd_data_restrictions=reject_unauth_pipelining
 
 420       #  -o smtpd_delay_reject=no
 
 421       #  -o smtpd_end_of_data_restrictions=
 
 422       #  -o smtpd_error_sleep_time=0
 
 423       #  -o smtpd_hard_error_limit=1000
 
 424       #  -o smtpd_helo_restrictions=
 
 426       #  -o smtpd_recipient_restrictions=permit_mynetworks,reject
 
 427       #  -o smtpd_restriction_classes=
 
 428       #  -o smtpd_sender_restrictions=
 
 429       #  -o smtpd_soft_error_limit=1001
 
 430       #  -o strict_rfc821_envelopes=yes
 
 431       #submission inet n       -       -       -       -       smtpd
 
 432       #  -o cleanup_service_name=pre-cleanup
 
 433       #  -o content_filter=amavis:[127.0.0.1]:10024
 
 434       #  -o milter_macro_daemon_name=ORIGINATING
 
 435       #  -o receive_override_options=no_address_mappings
 
 436       #  -o smtpd_sender_restrictions=permit_tls_clientcerts,reject
 
 437       #  -o smtpd_tls_ask_ccert=yes
 
 438       #  -o smtpd_tls_auth_only=yes
 
 439       #  -o smtpd_tls_ccert_verifydepth=2
 
 440       #  -o smtpd_tls_loglevel=1
 
 441       #  -o smtpd_tls_req_ccert=yes
 
 442       #  -o smtpd_tls_security_level=encrypt
 
 443       #smtps     inet  n       -       -       -       -       smtpd
 
 444       #  -o milter_macro_daemon_name=ORIGINATING
 
 445       #  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 
 446       #  -o smtpd_sasl_auth_enable=yes
 
 447       #  -o smtpd_tls_ask_ccert=yes
 
 448       #  -o smtpd_tls_auth_only=yes
 
 449       #  -o smtpd_tls_ccert_verifydepth=0
 
 450       #  -o smtpd_tls_loglevel=1
 
 451       #  -o smtpd_tls_req_ccert=no
 
 452       #  -o smtpd_tls_security_level=encrypt
 
 453       #  -o smtpd_tls_wrappermode=yes
 
 454       #pickup    fifo  n       -       -       60      1       pickup
 
 455       #  -o cleanup_service_name=pre-cleanup
 
 456       #  -o content_filter=amavis:[127.0.0.1]:10024
 
 457       #pre-cleanup unix n      -       -       -       0       cleanup
 
 458       #  -o virtual_alias_maps=
 
 459       #cleanup   unix  n       -       -       -       0       cleanup
 
 460       #  -o mime_header_checks=
 
 461       #  -o nested_header_checks=
 
 465       #sympa unix - n n - - pipe
 
 466       #  flags=R user=sympa argv=/usr/lib/sympa/bin/queue ''${recipient}
 
 467       #sympabounce unix - n n - - pipe
 
 468       #  flags=R user=sympa argv=/usr/lib/sympa/bin/bouncequeue ''${recipient}
 
 471      #noclue    unix  -       n       n       -       -       pipe
 
 472      #  flags=q user=noclue argv=/usr/local/bin/noclue-delivery ${recipient} ${sender}