# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4

PortSystem              1.0
PortGroup               active_variants 1.1

name                    mail-server
version                 1.5
revision                1
categories              mail net
platforms               darwin
supported_archs         noarch
maintainers             {ieee.org:s.t.smith @essandess} openmaintainer
license                 GPL-3
distfiles

description             Mail server configuration

long_description        Mail server working configuration that \
                        provides a basic, working, easily modifiable \
                        mail server. The configuration is built using \
                        postfix for the MTA, dovecot for the MDA, solr \
                        for fast search, rspamd for a milter, and \
                        clamav for email virus scans. The \
                        configuration includes a surrogate TLS \
                        certificate, DKIM, and Apple Push Notification \
                        Service (APNS) capability for iOS devices.

homepage                https://www.postfix.org/

set postfix_required_variants \
                        [lsort {dovecot_sasl pcre smtputf8 tls}]
set dovecot_required_variants \
                        [lsort {solr}]

depends_lib-append      port:dcc \
                        port:dovecot \
                        port:dovecot-sieve \
                        path:bin/openssl:openssl \
                        port:postfix \
                        port:rspamd \
                        port:sf-pwgen

# these dependencies are from variants that may not be active
depends_lib-append      port:apache-solr8 \
                        port:clucene \
                        port:curl \
                        port:expat \
                        port:pcre

depends_run-append      port:clamav-server

variant initialize_always \
    description {Always initialize all configuration files. Intended \
        for development and troubleshooting only. Working deployments \
        must disable this variant to prevent configuration files \
        being overwritten at the next upgrade. Existing configuration \
        files are not overwritten by default.} {
    ui_warn \
        "
\tAll configuration files will be initialized because
\tthe variant +initialize_always is set. Please disable
\tthis variant for working deployments.
"
}

variant logrotate \
    description {Use mail-server logrotate configuration.} {
    depends_lib-append  port:logrotate
}

default_variants-append +logrotate

use_configure           no

require_active_variants postfix ${postfix_required_variants}
require_active_variants dovecot ${dovecot_required_variants}

build {}

set certificates_dir ${prefix}/etc/certificates
set tls_ca_dir ${certificates_dir}/ca.macports

# random 4-word-based passphrase
proc correct_horse_battery_staple {} {
    # ignore errors from sf-pwgen if the password is shorter than requested
    return \
        [join [exec sh -c "sf-pwgen \
            --algorithm memorable --count 2 --length 16 \
            2>/dev/null || true"] -]
}

destroot {
    # configuration design: MacPorts file and/or directory templates installed
    # to *.macports, then edited with local network settings, then in
    # post-activate copied to actual configuration files if such don't exist

    # common
    foreach d [list \
        var/log/mail \
        etc/certificates \
        var/spool/postfix${prefix}/var/run/rspamd \
        var/run/redis \
        ] {
        xinstall -m 0750 -o root -g mail -d ${destroot}${prefix}/${d}
        destroot.keepdirs-append ${destroot}${prefix}/${d}
    }

    # postfix
    xinstall -m 0755 -d ${destroot}${prefix}/etc/postfix
    xinstall -m 0755 -d ${destroot}${prefix}/etc/postfix/etc/pam.d
    xinstall -m 0750 -o root -g _postfix -d ${destroot}${prefix}/etc/postfix/sasl
    xinstall -m 0755 -o root -g _postfix -d ${destroot}${prefix}/var/spool/postfix/etc
    xinstall -m 0755 -o root -g mail -d \
        ${destroot}${prefix}/etc/postfix/etc \
        ${destroot}${prefix}/etc/postfix/etc/certificates \
        ${destroot}${prefix}/etc/postfix/etc/pam.d
    # *.macports templates
    foreach f {
        main.cf
        master.cf
        sasl/passwd
        } {
        xinstall -m 0644 \
            ${filespath}/prefix/etc/postfix/${f} \
            ${destroot}${prefix}/etc/postfix/${f}.macports
    }
    # generic templates
    foreach f {
        master.cf.chroot
        master.cf.nochroot
        smtp.keytab.README.sh
        etc/pam.d/smtp
        } {
        xinstall -m 0644 \
            ${filespath}/prefix/etc/postfix/${f} \
            ${destroot}${prefix}/etc/postfix/${f}
    }
    # chroot jail necessities
    xinstall -m 0644 /etc/hosts ${destroot}${prefix}/var/spool/postfix/etc
    xinstall -m 0644 /etc/services ${destroot}${prefix}/var/spool/postfix/etc

    # dovecot
    xinstall -m 0755 -d ${destroot}${prefix}/etc/dovecot
    # *.macports templates
    foreach d {
        conf.d
        } {
        xinstall -m 0755 -d \
            ${destroot}${prefix}/etc/dovecot/${d}.macports
        destroot.keepdirs-append \
            ${destroot}${prefix}/etc/dovecot/${d}.macports
    }
    foreach d {
        sieve
        sieve-before.d
        sieve-afer.d
        } {
        xinstall -m 0755 -g mail -d \
            ${destroot}${prefix}/etc/dovecot/${d}.macports
        destroot.keepdirs-append \
            ${destroot}${prefix}/etc/dovecot/${d}.macports
    }
    foreach f {
        dovecot.conf
        } {
        xinstall -m 0644 \
            ${filespath}/prefix/etc/dovecot/${f} \
            ${destroot}${prefix}/etc/dovecot/${f}.macports
    }
    foreach d {
        conf.d
        sieve
        sieve-before.d
        sieve-afer.d
        } {
        foreach f [glob -nocomplain ${filespath}/prefix/etc/dovecot/${d}/*] {
            if {[file isfile ${f}]} {
                xinstall -m 0644 ${f} \
                    ${destroot}${prefix}/etc/dovecot/${d}.macports/[file tail ${f}]
            }
        }
    }

    # generic templates
    foreach d {
        default
        example-config
        etc/pam.d
        } {
        xinstall -m 0755 -d \
            ${destroot}${prefix}/etc/dovecot/${d}
    }
    foreach f {
        etc/pam.d/imap
        imap.keytab.README.sh
        } {
        xinstall -m 0644 \
            ${filespath}/prefix/etc/dovecot/${f} \
            ${destroot}${prefix}/etc/dovecot/${f}
    }
    foreach d {
        default
        example-config
        } {
        foreach f [glob -nocomplain ${filespath}/prefix/etc/dovecot/${d}/*] {
            if {[file isfile ${f}]} {
                xinstall -m 0644 ${f} \
                    ${destroot}${prefix}/etc/dovecot/${d}/[file tail ${f}]
            }
        }
    }

    # rspamd
    xinstall -m 0755 -d ${destroot}${prefix}/etc/rspamd
    foreach d {
        local.d
        } {
        xinstall -m 0755 -d ${destroot}${prefix}/etc/rspamd/${d}.macports
        foreach f [glob -nocomplain ${filespath}/prefix/etc/rspamd/${d}/*] {
            if {[file isfile ${f}]} {
                xinstall -m 0644 ${f} \
                    ${destroot}${prefix}/etc/rspamd/${d}.macports/[file tail ${f}]
            }
        }
    }
    foreach f {
        dkim_paths.map
        dkim_selectors.map
        } {
        xinstall -m 0644 ${filespath}/prefix/etc/rspamd/${f} \
            ${destroot}${prefix}/etc/rspamd/${f}.macports
    }

    # redis
    foreach f {
        redis.conf
        } {
        xinstall -m 0644 ${filespath}/prefix/etc/${f} \
            ${destroot}${prefix}/etc/${f}.macports
    }

    # dcc
    xinstall -m 0755 -d ${destroot}${prefix}/etc/dcc
    foreach f {
        dcc_conf
        } {
        xinstall -m 0644 ${filespath}/prefix/etc/dcc/${f} \
            ${destroot}${prefix}/etc/dcc/${f}.macports
    }

    # logrotate
    if { [variant_isset "logrotate"] } {
        foreach d {
            logrotate.d
            } {
            xinstall -m 0755 -d ${destroot}${prefix}/etc/${d}.macports
            foreach f [glob -nocomplain ${filespath}/prefix/etc/${d}/*] {
                if {[file isfile ${f}]} {
                    xinstall -m 0644 ${f} \
                        ${destroot}${prefix}/etc/${d}.macports/[file tail ${f}]
                }
            }
        }
        foreach f {
            logrotate.conf
            } {
            xinstall -m 0644 ${filespath}/prefix/etc/${f} \
                ${destroot}${prefix}/etc/${f}.macports
        }
    }

    # TLS certificate surrogate
    xinstall -m 0755 -d ${destroot}${certificates_dir}
    xinstall -m 0700 -d ${destroot}${certificates_dir}/private
    destroot.keepdirs-append \
        ${destroot}${certificates_dir} \
        ${destroot}${certificates_dir}/private
    xinstall -m 0755 -d ${destroot}${tls_ca_dir}
    xinstall -m 0755 -d ${destroot}${tls_ca_dir}/intermediate
    xinstall -m 0644 \
        ${filespath}/prefix/etc/certificates/ca/openssl.cnf \
        ${destroot}${tls_ca_dir}
    xinstall -m 0644 \
        ${filespath}/prefix/etc/certificates/ca/intermediate/openssl_intermediate.cnf \
        ${destroot}${tls_ca_dir}/intermediate
    if { [variant_isset "initialize_always"]
         && [file exists ${tls_ca_dir}]
        } {
        delete ${tls_ca_dir}.previous
        move \
            ${tls_ca_dir} \
            ${tls_ca_dir}.previous
    }
}

destroot.keepdirs ${destroot}${prefix}/var/log/mail

# Workaround for issue with dovecot version 2.3.17 on macOS 12
# https://www.mail-archive.com/dovecot@dovecot.org/msg84784.html
if {${os.platform} eq "darwin" && ${os.major} >= 21} {
    post-destroot {
        system -W ${destroot}${prefix}/etc/dovecot/conf.d.macports \
            "${patch.cmd} ${patch.pre_args} < ${filespath}/patch-10-master.conf.diff"
    }
}

pre-activate {
    # mail-server 1.1 inadvertently installed org.macports.logrotate.plist
    # into /Library/LaunchDaemons
    # https://trac.macports.org/ticket/60273
    # This cleanup hack can be removed after December 2023.
    if { ![variant_isset "logrotate"] } {
        delete /Library/LaunchDaemons/org.macports.logrotate.plist
    }
}

proc plutil_startup {plcmds label} {
    global prefix startupitem.location
    foreach cmd ${plcmds} {
        system -W ${prefix}/etc/${startupitem.location}/${label} \
            "/usr/bin/plutil ${cmd} ${label}.plist"
    }
}

# Network configuration
# hard-coded examples
set host                host
set domain              domain
set tld                 tld
set fullhost            ${host}.${domain}.${tld}
set domaintld           ${domain}.${tld}
set HOST                [string toupper ${host}]
set DOMAIN              [string toupper ${domain}]
set TLD                 [string toupper ${tld}]
set FULLHOST            [string toupper ${fullhost}]
set DOMAINTLD           [string toupper ${domaintld}]
set relayhost           mymailrelay.tld

post-activate {
    # modify the launch daemons
    plutil_startup [list \
        "-remove KeepAlive" \
        "-insert RunAtLoad -bool YES" \
        ] \
        org.macports.${name}
    # Cf. port logrotate's ${prefix}/share/logrotate/org.macports.logrotate.plist.example
    if { [variant_isset "logrotate"] } {
        plutil_startup [list \
            "-remove KeepAlive" \
            "-insert RunAtLoad -bool YES" \
            "-replace ProgramArguments \
                -xml '<array> \
                        <string>${prefix}/sbin/logrotate</string> \
                        <string>${prefix}/etc/logrotate.conf</string> \
                      </array>'" \
            "-insert StartCalendarInterval \
                -xml '<dict> \
                        <key>Hour</key> \
                        <integer>5</integer> \
                        <key>Minute</key> \
                        <integer>30</integer> \
                      </dict>'" \
            ] \
            org.macports.${name}.logrotate
    }

    # use network settings for installed example configuration
    set fullhost [exec /bin/hostname -f]
    if { [llength [split ${fullhost} .]] >= 3 } {
        set host [lindex [split ${fullhost} .] 0]
        set domaintld [join [lrange [split ${fullhost} .] 1 end] .]
        set domain [lindex [split ${domaintld} .] 0]
        set tld [lindex [split ${domaintld} .] end]
    }
    set HOST            [string toupper ${host}]
    set DOMAIN          [string toupper ${domain}]
    set TLD             [string toupper ${tld}]
    set FULLHOST        [string toupper ${fullhost}]
    set DOMAINTLD       [string toupper ${domaintld}]
    set rspamd_control_password \
        [correct_horse_battery_staple]
    set rspamd_control_password_hash \
        [exec rspamadm pw --password ${rspamd_control_password}]

    ui_msg "Configuring Mail Server with:

        host :                         ${host}
        domain :                       ${domain}
        tld :                          ${tld}
"

    proc install_initial_configuration {f_or_d} {
        if { [variant_isset "initialize_always"]
             && [file exists ${f_or_d}]
            } {
            delete ${f_or_d}.previous
            move \
                ${f_or_d} \
                ${f_or_d}.previous
        }
        if { [variant_isset "initialize_always"]
             || ![file exists ${f_or_d}]
            } {
            if { [file isfile ${f_or_d}.macports] } {
                xinstall -m 0644 \
                    ${f_or_d}.macports \
                    ${f_or_d}
            } elseif { [file isdirectory ${f_or_d}.macports] } {
                xinstall -m 0755 -d ${f_or_d}
                foreach f [glob -nocomplain ${f_or_d}.macports/*] {
                    xinstall -m 0644 ${f} \
                        ${f_or_d}/[file tail ${f}]
                }
            }
        }
    }

    # postfix configuration
    foreach f_or_d {
        main.cf
        master.cf
        sasl/passwd
        } {
        install_initial_configuration ${prefix}/etc/postfix/${f_or_d}
    }
    # postfix relay host and password surrogate
    file attributes ${prefix}/etc/postfix/sasl/passwd \
        -group _postfix -permissions 0640

    # dovecot configuration
    foreach f_or_d {
        dovecot.conf
        conf.d
        sieve
        sieve-before.d
        sieve-afer.d
        } {
        install_initial_configuration ${prefix}/etc/dovecot/${f_or_d}
    }
    xinstall -m 0770 -g mail -d /private/var/mail/${tld}.${domain}.mail/
    xinstall -m 0777 -g mail -d /private/var/mail/${tld}.${domain}.mail/attachments/

    # solr configuration
    if { [variant_isset "initialize_always"] } {
        system "sudo -u solr -g solr sh <<SOLR_DELETE_DOVECOT
            solr8 stop -p 8983 2>/dev/null || true
            solr8 start -p 8983 2>/dev/null || true
            solr8 delete -c dovecot 2>/dev/null || true
            solr8 stop -p 8983 2>/dev/null || true
SOLR_DELETE_DOVECOT
"
    }
    # create dovecot core; wrap commands in shell to avoid non-zero
    # return value if it already exists
    system "sudo -u solr -g solr sh -c \
        \"solr8 stop -p 8983 2>/dev/null || true\""
    system "sudo -u solr -g solr sh -c \
        \"solr8 start -p 8983 2>/dev/null || true\""
    set solr_version [exec solr8 version]
    system "sudo -u solr -g solr sh -c \
        \"solr8 create -c dovecot -n dovecot 2>/dev/null || true\""
    system "sudo -u solr -g solr sh -c \
        \"solr8 stop -p 8983 2>/dev/null || true\""
    if { ![file exists ${prefix}/var/solr/dovecot/conf/] } {
        ui_error \
            "solr directory ${prefix}/var/solr/dovecot/conf/ doesn't exist."
        exit 1
    }
    # See Tips at https://wiki.dovecot.org/Plugins/FTS/Solr
    # wget -O solrconfig.xml https://github.com/dovecot/core/raw/master/doc/solr-config-7.7.0.xml
    xinstall -b -B.orig -g solr -m 0644 -o solr \
        ${filespath}/prefix/var/solr/dovecot/conf/solrconfig.xml \
        ${prefix}/var/solr/dovecot/conf/solrconfig.xml
    # wget -O schema.xml https://github.com/dovecot/core/raw/master/doc/solr-schema-7.7.0.xml
    xinstall -b -B.orig -g solr -m 0644 -o solr \
        ${filespath}/prefix/var/solr/dovecot/conf/schema.xml \
        ${prefix}/var/solr/dovecot/conf/schema.xml
    # Note: solr will replace schema.xml with managed-schema
    if { [file exists ${prefix}/var/solr/dovecot/conf/managed-schema.orig] } {
        delete ${prefix}/var/solr/dovecot/conf/managed-schema.orig
    }
    move ${prefix}/var/solr/dovecot/conf/managed-schema \
        ${prefix}/var/solr/dovecot/conf/managed-schema.orig
    # Edit in the Lucene version; see lucene-spec at http://localhost:8983/solr
    reinplace -E -q \
        "s|(<luceneMatchVersion>)\[\[:digit:\]\]\\.\[\[:digit:\]\]\\.\[\[:digit:\]\](</luceneMatchVersion>)|\\1${solr_version}\\2|" \
        ${prefix}/var/solr/dovecot/conf/solrconfig.xml
    reinplace -E -q \
        "s|<str name=\"df\">|<str name=\"hdr\">|" \
        ${prefix}/var/solr/dovecot/conf/solrconfig.xml

    # rspamd configuration
    if { ![file exists ${prefix}/var/lib/rspamd/dkim] } {
        xinstall -m 0750 -o _rspamd -g _rspamd -d \
            ${prefix}/var/lib/rspamd/dkim
    }
    foreach f_or_d {
        local.d
        dkim_paths.map
        dkim_selectors.map
        } {
        install_initial_configuration ${prefix}/etc/rspamd/${f_or_d}
    }
    foreach f [list \
        ${prefix}/var/lib/rspamd/dkim/${domaintld}.dkim_rsa2048.key \
        ${prefix}/var/lib/rspamd/dkim/${domaintld}.dkim_ed25519.key
        ] {
        if { ![file exists ${f}] } {
            set f_txt [strsed ${f} {s|\.key$|.txt|}]
            if { [string match "*rsa2048*" ${f}] } {
                system -W ${prefix}/var/lib/rspamd/dkim \
                    "sh -c \"rspamadm dkim_keygen \
                        -k ${f} -s dkim_rsa2048 -t rsa -b 2048 \
                        -d ${domaintld} \
                        1>${f_txt}\""
            } elseif { [string match "*ed25519*" ${f}] } {
                system -W ${prefix}/var/lib/rspamd/dkim \
                    "sh -c \"rspamadm dkim_keygen \
                        -k ${f} -s dkim_ed25519 -t ed25519 \
                        -d ${domaintld} \
                        1>${f_txt}\""
            }
            file attributes ${f} \
                -owner _rspamd -group _rspamd -permissions 0640
            file attributes ${f_txt} \
                -owner _rspamd -group _rspamd -permissions 0644
        }
    }

    # redis configuration
    foreach f_or_d {
        redis.conf
        } {
        install_initial_configuration ${prefix}/etc/${f_or_d}
    }

    # dcc configuration
    foreach f_or_d {
        dcc_conf
        } {
        install_initial_configuration ${prefix}/etc/dcc/${f_or_d}
    }

    # logrotate configuration
    if { [variant_isset "logrotate"] } {
        foreach f_or_d {
            logrotate.conf
            logrotate.d
            } {
            install_initial_configuration ${prefix}/etc/${f_or_d}
        }
    }

    # TLS certificate surrogate -- certificate authority chain of trust
    xinstall -m 0700 -d ${tls_ca_dir}/private
    xinstall -m 0700 -d ${tls_ca_dir}/intermediate/private
    foreach f { \
        openssl.cnf \
        intermediate/openssl_intermediate.cnf \
        } {
        reinplace "s|@TLS_CA_DIR@|${tls_ca_dir}|g" ${tls_ca_dir}/${f}
    }
    # CA and Intermediate CA encrypted key passphrases in ./private
    # CA passphrase
    set tls_ca_passphrase \
        [correct_horse_battery_staple]
    set tls_ca_passphrase_fd \
        [open ${tls_ca_dir}/private/passphrase.txt w 0600]
    puts ${tls_ca_passphrase_fd} \
        ${tls_ca_passphrase}
    close ${tls_ca_passphrase_fd}
    # Intermediate CA passphrase
    set tls_intermediate_ca_passphrase \
        [correct_horse_battery_staple]
    set tls_intermediate_ca_passphrase_fd \
        [open ${tls_ca_dir}/intermediate/private/passphrase_intermediate.txt w 0600]
    puts ${tls_intermediate_ca_passphrase_fd} \
        ${tls_intermediate_ca_passphrase}
    close ${tls_intermediate_ca_passphrase_fd}
    # Client certificate passphrase
    set tls_client_certificate_passphrase \
        [correct_horse_battery_staple]
    set tls_client_certificate_passphrase_fd \
        [open ${tls_ca_dir}/intermediate/private/passphrase_client.txt w 0600]
    puts ${tls_client_certificate_passphrase_fd} \
        ${tls_client_certificate_passphrase}
    close ${tls_client_certificate_passphrase_fd}
    # create the chain of trust
    system -W ${tls_ca_dir} \
        "sh <<TLS_CERTIFICATE_SURROGATE
            # CA encrypted key
            openssl genpkey -out private/ca.key.pem \\
                -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -aes256 \\
                -pass file:private/passphrase.txt
            chmod go-r private/ca.key.pem

            # CA certificate
            openssl req -config openssl.cnf \\
                -new -x509 -days 1460 -sha384 \\
                -extensions v3_ca \\
                -out ca.cert.pem -key private/ca.key.pem \\
                -passin file:private/passphrase.txt -batch

            # CA certificate openssl self-verification
            openssl verify -CAfile ca.cert.pem ca.cert.pem

            ##################
            # Intermediate CA
            ##################

            # Intermediate CA encrypted key
            openssl genpkey \\
                -out intermediate/private/intermediate.key.pem \\
                -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -aes256 \\
                -pass file:intermediate/private/passphrase_intermediate.txt
            chmod go-r intermediate/private/intermediate.key.pem

            # Intermediate CA CSR
            openssl req -config intermediate/openssl_intermediate.cnf \\
                -new -sha384 \\
                -key intermediate/private/intermediate.key.pem \\
                -passin file:intermediate/private/passphrase_intermediate.txt \\
                -out intermediate/intermediate.csr.pem -batch

            # CA initialize database
            echo 01 > serial
            touch index.txt

            # Intermediate CA certificate
            openssl ca -config openssl.cnf \\
                -days 730 -notext -md sha384 -extensions v3_intermediate_ca \\
                -in intermediate/intermediate.csr.pem \\
                -out intermediate/intermediate.cert.pem \\
                -passin file:private/passphrase.txt -batch

            # Intermediate CA chain
            cat intermediate/intermediate.cert.pem ca.cert.pem \\
                > intermediate/ca-chain.cert.pem

            # Intermediate CA chain openssl verification
            openssl verify -CAfile ca.cert.pem intermediate/ca-chain.cert.pem

            ##################
            # Client Certs
            ##################

            # Client certificate encrypted key
            openssl genpkey \\
                -out intermediate/private/${fullhost}.key.pem \\
                -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -aes256 \\
                -pass file:intermediate/private/passphrase_client.txt
            chmod go-r intermediate/private/${fullhost}.key.pem

            # Client certificate decrypted key
            openssl pkey -in intermediate/private/${fullhost}.key.pem \\
                -passin file:intermediate/private/passphrase_client.txt \\
                -out intermediate/private/${fullhost}.key.pem.decrypted
            chmod go-r intermediate/private/${fullhost}.key.pem.decrypted

            # Client certificate CSR
            openssl req -config intermediate/openssl_intermediate.cnf \\
                -new -sha384 \\
                -key intermediate/private/${fullhost}.key.pem \\
                -passin file:intermediate/private/passphrase_client.txt \\
                -out intermediate/${fullhost}.csr.pem -batch

            # Intermediate CA initialize database
            echo 01 > intermediate/serial
            touch intermediate/index.txt

            # Client certificate
            openssl ca -config intermediate/openssl_intermediate.cnf \\
                -days 375 -notext -md sha384 \\
                -in intermediate/${fullhost}.csr.pem \\
                -out intermediate/${fullhost}.cert.pem \\
                -passin file:intermediate/private/passphrase_intermediate.txt \\
                -subj '/CN=${fullhost}' -batch

            # Client chain openssl verification
            openssl verify -CAfile intermediate/ca-chain.cert.pem \\
                intermediate/${fullhost}.cert.pem

            # Client chain of trust
            cat intermediate/${fullhost}.cert.pem \\
                intermediate/intermediate.cert.pem ca.cert.pem \\
                > intermediate/${fullhost}.chain.pem

            # Client certificate of the forms
            # @host@.@domain@.@tld@.@CERTIFICATE_SHA1@.{cert,key,chain}.pem
TLS_CERTIFICATE_SURROGATE
"
    set certificate_sha1 [exec \
        openssl x509 -noout -fingerprint -sha1 -inform pem \
            -in ${tls_ca_dir}/intermediate/${fullhost}.cert.pem]
    regsub -nocase "^sha1 Fingerprint=" ${certificate_sha1} "" certificate_sha1
    set certificate_sha1 [string tolower [strsed ${certificate_sha1} "g|:||"]]
    xinstall -m 0600 \
        ${tls_ca_dir}/intermediate/private/${fullhost}.key.pem.decrypted \
        ${certificates_dir}/private/${fullhost}.${certificate_sha1}.key.pem.decrypted
    xinstall -m 0600 \
        ${tls_ca_dir}/intermediate/private/${fullhost}.key.pem \
        ${certificates_dir}/private/${fullhost}.${certificate_sha1}.key.pem
    xinstall -m 0600 \
        ${tls_ca_dir}/intermediate/private/passphrase_client.txt \
        ${certificates_dir}/private/${fullhost}.${certificate_sha1}.key.passphrase
    xinstall -m 0644 \
        ${tls_ca_dir}/intermediate/${fullhost}.cert.pem \
        ${certificates_dir}/${fullhost}.${certificate_sha1}.cert.pem
    xinstall -m 0644 \
        ${tls_ca_dir}/intermediate/${fullhost}.chain.pem \
        ${certificates_dir}/${fullhost}.${certificate_sha1}.chain.pem

    # configure all template files with local settings
    set d_or_f_templates { \
        postfix \
        dovecot \
        rspamd \
        redis.conf \
    }
    if { [variant_isset "logrotate"] } {
        append d_or_f_templates { \
            logrotate.conf \
            logrotate.d \
        }
    }
    foreach d_or_f ${d_or_f_templates} {
        fs-traverse f ${prefix}/etc/${d_or_f} {
            if { [file isfile ${f}]
                 && ![string match ".macports" ${f}]
                 && [string match "text/*" \
                     [lindex [exec /usr/bin/file --mime-type ${f}] end]]
            } then {
                foreach cmd [list \
                    "s|@PREFIX@|${prefix}|g" \
                    "s|@host@.@domain@.@tld@|${fullhost}|g" \
                    "s|@domain@.@tld@|${domaintld}|g" \
                    "s|@host@|${host}|g" \
                    "s|@domain@|${domain}|g" \
                    "s|@tld@|${tld}|g" \
                    "s|@HOST@.@DOMAIN@.@TLD@|${FULLHOST}|g" \
                    "s|@DOMAIN@.@TLD@|${DOMAINTLD}|g" \
                    "s|@HOST@|${HOST}|g" \
                    "s|@DOMAIN@|${DOMAIN}|g" \
                    "s|@TLD@|${TLD}|g" \
                    "s|@RSPAMD_CONTROL_PASSWORD@|${rspamd_control_password}|g" \
                    "s|@RSPAMD_CONTROL_PASSWORD_HASH@|${rspamd_control_password_hash}|g" \
                    "s|@CERTIFICATE_SHA1@|${certificate_sha1}|g" \
                    "s|@RELAYHOST@|${relayhost}|g" \
                    ] {
                    reinplace -q ${cmd} ${f}
                }
            }
        }
    }

    # postfix relay host and password surrogate
    system -W ${prefix}/etc/postfix/sasl "postmap passwd"
    # postfix permissions
    # system -W ${prefix}/etc/postfix "postfix set-permissions"

    # dovecot sieve functions
    # dovecot must be built with `--with-lucene` for sievec to work here
    if { ![catch {set result [registry_active dovecot]}]
         && [string match "*solr*" [lindex [lindex ${result} 0] 3]] } {
        foreach d {
            sieve
            sieve-before.d
            sieve-afer.d
            } {
            foreach f [glob -nocomplain ${prefix}/etc/dovecot/${d}/*.sieve] {
                system -W ${prefix}/etc/dovecot/${d} "sievec ${f}"
            }
        }
    } else {
        ui_msg "dovecot plugin 'fts_lucene' not installed. Please install:

    sudo port -pN install dovecot +[join ${dovecot_required_variants} +]

and rerun `sudo port install ${name}`. Ensure that the sieve scripts
in ${prefix}/etc/dovecot/sieve*/*.sieve are compiled with sievec.
"
    }

    # PAM authentication
    if { ![file exists /etc/pam.d/smtp] } {
        xinstall -m 0644 ${prefix}/etc/postfix/etc/pam.d/smtp /etc/pam.d/
    }
    if { ![file exists /etc/pam.d/imap] } {
        xinstall -m 0644 ${prefix}/etc/dovecot/etc/pam.d/imap /etc/pam.d/
    }

    # TLS PFS
    if { ![file exists ${prefix}/var/lib/postfix/dh2048.pem] } {
        system -W ${prefix}/var/lib/postfix "sudo -u _postfix openssl dhparam -out dh2048.pem 2048"
    }
    if { ![file exists ${prefix}/etc/dovecot/dh2048.pem] } {
        # create a shorter, faster DH parameter file for the default installation
        system -W ${prefix}/etc/dovecot "openssl dhparam -out dh2048.pem 2048"
    }

    # mail group membership
    # dscacheutil -q group -a name mail
    foreach u {
        _postfix
        _dovecot
        _dovenull
        _rspamd
        } {
        system "dseditgroup -o edit -a ${u} -t user mail"
    }
}

startupitem.create      yes
startupitems \
        name            ${name} \
        start {
                        "port load clamav-server"
                        "port load apache-solr8"
                        "port load redis"
                        "port load dcc"
                        "port load postfix"
                        "port load dovecot"
                        "port load rspamd"
        } \
        stop {
                        "port unload apache-solr8"
                        "port unload dcc"
                        "port unload postfix"
                        "port unload dovecot"
                        "port unload rspamd"
        } \
        restart {
                        "port reload apache-solr8"
                        "port reload redis"
                        "port reload dcc"
                        "port unload postfix"
                        "sleep 1"
                        "port load postfix"
                        "port unload dovecot"
                        "sleep 1"
                        "port load dovecot"
                        "port reload rspamd"
        }

if { [variant_isset "logrotate"] } {
    startupitems-append \
        name            ${name}.logrotate \
        executable      ${prefix}/sbin/logrotate
}

notes "A mail server is a complex, interdependent set of tools that must \
all be configured correctly to provide secure, reliable email.

Users must reconfigure this installation for their own system, network, \
and security model specifics by editing all necessary files and checking \
file permissions. A subset of these settings are visible in the files:

        port contents mail-server
        port file mail-server

Full deployment also requires a working DNS configuration on both the LAN \
and the internet, including SPF and DKIM records, trusted TLS certificates, \
port forwarding, possibly a mail replay, and more.

Postfix and dovecot must be installed with these variants:
        sudo port -pN install postfix +[join ${postfix_required_variants} +]
        sudo port -pN install dovecot +[join ${dovecot_required_variants} +] \[+apns]

These are the locations and network settings for the default configuration:

    MTA (postfix):
        ${prefix}/etc/postfix/main.cf
        ${prefix}/etc/postfix/master.cf
        ports: smtp/tcp (25), submission/tcp (587)

    MDA (dovecot):
        ${prefix}/etc/dovecot/dovecot.conf
        ${prefix}/etc/dovecot/conf.d/*
        port: imaps/tcp (993)

    FTS (solr):
        http://localhost:8983/

    Milter (rspamd):
        ${prefix}/etc/rspamd/rspamd.conf
        ${prefix}/etc/rspamd/local.d/*

    A default Rspamd controller password and its hash appear in the files:
        ${prefix}/etc/dovecot/sieve/train-spam.sh
        ${prefix}/etc/dovecot/sieve/train-ham.sh
        ${prefix}/etc/rspamd/local.d/worker-controller.inc

    Rspamd controller:
        http://localhost:11334/

    Spam/Ham training (default behavior):
        Move/Copy email to the folders Spam_train or Notspam_train.

The configuration also includes a surrogate TLS certificate and DKIM settings \
that must be changed before deployment.

    TLS:
        ${prefix}/etc/certificates

    DKIM:
        ${prefix}/var/lib/rspamd/dkim

The ports dns-server provide necessary DNS service on the LAN; variant \
+logrotate provides log rotation capabilities:

        sudo port install dns-server
        sudo port install mail-server +logrotate

This port assume indepedent installation and management of DNS and \
log rotation; mail-server includes example logrotate configuration files \
and a logroate launchdaemon.

The port's launch daemon controls launching for each of the dependendent \
services. These may be controlled independently, e.g.

        sudo port load clamav-server
        sudo port load apache-solr8
        sudo port load redis
        sudo port load dcc
        sudo port load postfix
        sudo port load dovecot
        sudo port load rspamd

and if installed independently,

        sudo port load dns-server
        sudo port load logrotate

TLS certificate updates must be included in mail-server dovecot's \
conf.d/10-ssl.conf, postfix's master.cf, and, if installed, \
calendar-contacts-server's proxy nginx.conf. Instructions are \
included as comments in:

    sudo vi ${prefix}/etc/dovecot/conf.d/10-ssl.conf
    sudo vi ${prefix}/etc/postfix/main.cf
    sudo vi \\
        ${prefix}/var/calendarserver/Library/CalendarServer/etc/nginx.conf

    References:
        * http://www.postfix.org/documentation.html
        * https://wiki.dovecot.org/
        * https://www.rspamd.com/doc/index.html
        * https://www.c0ffee.net/blog/mail-server-guide/
        * _The Book of Postfix_, by Patrick Koetter and Ralf Hildebrandt

    Known issues:
        * The Postfix service does not reliably start after reboot, \
          presumably due to an issue with launchd. A workaround \
          after rebooting is to issue the commands:

          sudo port unload postfix ; sleep 5 ; sudo port load postfix"

if { [variant_isset "initialize_always"] } {
    if {[exists notes]} {
        # leave a blank line after the existing notes
        notes-append ""
    }
    notes-append \
        "The variant +initialize_always is set, which initializes \
        all configuration files. Please disable this variant for \
        working deployments."
}

livecheck.type         none