base.m4: Line-wrap the DKIM warning header. master
authorMark Wooding <mdw@distorted.org.uk>
Sat, 11 May 2024 13:30:28 +0000 (14:30 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 11 May 2024 13:30:28 +0000 (14:30 +0100)
Makefile
base.m4
config.m4
defs.m4
lists.m4
site.mk [new file with mode: 0644]
spam.m4
vhost-local.m4

index 46aa594..a779e41 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -70,6 +70,7 @@ OPTIONS_hub           += user-spam.m4 vhost.m4 vhost-local.m4
 MODES                  += usersat
 OPTIONS_usersat                 = auth.m4 local.m4 satellite.m4
 
+-include site.mk
 -include local.mk
 
 ###--------------------------------------------------------------------------
diff --git a/base.m4 b/base.m4
index f8b09e4..0aadb78 100644 (file)
--- a/base.m4
+++ b/base.m4
@@ -44,6 +44,7 @@ extra_local_interfaces = <; 0.0.0.0 ; ::0
 
 SECTION(global, resource)m4_dnl
 deliver_queue_load_max = 8
+message_size_limit = 500M
 queue_only_load = 12
 smtp_accept_max = 16
 smtp_accept_queue = 32
@@ -127,7 +128,7 @@ SECTION(acl, misc)m4_dnl
 helo:
        ## Don't worry if this is local submission.  MUAs won't necessarily
        ## have a clear idea of their hostnames.  (For some reason.)
-       accept   condition = ${if !eq{$acl_c_mode}{submission}}
+       accept   condition = ${if eq{$acl_c_mode}{submission}}
 
        ## Check that the caller's claimed identity is actually plausible.
        ## This seems like it's a fairly effective filter on spamminess, but
@@ -212,8 +213,7 @@ mail_check_sender:
 
        ## See whether there's a special exception for this sender domain.
        accept   senders = ${LOOKUP_DOMAIN($sender_address_domain,
-                            {KV(senders, {$value}{})},
-                            {})}
+                            {KV(senders)})}
 
        ## Ensure that the sender is routable.  This is important to prevent
        ## undeliverable bounces.
@@ -419,6 +419,64 @@ m4_define(<:APPLY_HEADER_CHANGES:>,
                <:${if def:acl_m_hdrrm{$acl_m_hdrrm:}}\
                $2:>):>)
 
+m4_define(<:DKIM_SIGN_P:>,
+       <:and {{exists{CONF_sysconf_dir/dkim-sign.conf}} \
+              {!def:h_DKIM-Signature:} \
+              {!def:h_List-ID:} \
+              {or {{def:authenticated_id} \
+                   {def:authenticated_sender}}}}:>)
+
+m4_define(<:DKIM_KEYS_INSTANCE:>,
+       <:${lookup {${domain:$h_From:}} partial0-lsearch \
+                       {CONF_sysconf_dir/dkim-sign.conf} \
+               _LOOKUP_ARGS(<:$1:>, <:$2:>)}:>)
+m4_define(<:DKIM_KEYS_STATE:>, <:${lookup {$1} lsearch \
+               {DKIM_KEYS_INSTANCE(<:{CONF_dkim_keys_dir/$value/active/dkim-keys.state}:>)} \
+       _LOOKUP_ARGS(<:$2:>, <:$3:>, <:fail:>)}:>)
+m4_define(<:DKIM_KEYS_INFO:>, <:DKIM_KEYS_STATE(<:params:>,
+       <:{${if and {{>={$tod_epoch}{KV(t0)}} \
+                   {<{$tod_epoch}{${eval:KV(t0) + KV(n)*KV(step)}}}} \
+               {DKIM_KEYS_STATE(<:info.${eval:($tod_epoch - KV(t0))/KV(step)}:>,
+                       <:$1:>, <:$2:>)} \
+               m4_ifelse(<:$2:>, <::>, <:fail:>, <:$2:>)}}:>,
+       m4_ifelse(<:$2:>, <::>, <:fail:>, <:$2:>)):>)
+
+m4_define(<:DKIM_SIGN:>,
+       <:dkim_domain = \
+               ${if DKIM_SIGN_P \
+                       {DKIM_KEYS_INSTANCE({${domain:$h_From:}})}}
+       dkim_selector = DKIM_KEYS_INFO(<:{KV(k)}:>)
+       dkim_private_key = \
+               DKIM_KEYS_INSTANCE(<:m4_dnl
+                       {CONF_dkim_keys_dir/$value/active/$dkim_selector.priv}:>)
+       dkim_canon = relaxed
+       dkim_strict = true
+       ## The following ridiculous stunt does two important jobs.  Firstly,
+       ## and more obviously, it arranges to include one more copy of each
+       ## header name than the message actually contains, thereby causing
+       ## the signature to fail if another header with the same name is
+       ## added.  And secondly, and far more subtly, it also trims the
+       ## spaces from the header names so that they're in the format that
+       ## the signing machinery secretly wants.
+       dkim_sign_headers = \
+               ${sg {${map {CONF_dkim_headers : \
+                            X-CONF_header_token-DKIM-Key-Publication} \
+                           {$item${sg {${expand:\$h_$item:}\n} \
+                                      {((?:[^\n]+|\n\\s+)*)\n} \
+                                      {:$item}}}}} \
+                    {::}{:}}
+       headers_add = \
+               ${if DKIM_SIGN_P \
+                       {DKIM_KEYS_INFO(<:m4_dnl
+                               {X-CONF_header_token-DKIM-Key-Publication: \
+                                       DKIM signature not suitable \
+                                       as evidence after delivery;\n\t\
+                                       DKIM private key KV(k) will be \
+                                       published\n\t\
+                                       at KV(u)\n\t\
+                                       on or before KV(tpub)}:>)}}:>)
+
+
 m4_define(<:SMTP_DELIVERY:>,
        <:## Prevent sending messages with overly long lines.  The use of
        ## `message_size_limit' here is somewhat misleading.
@@ -433,7 +491,9 @@ SECTION(transports)m4_dnl
 ## it into the transport name.  This is very unpleasant, of course.
 smtp:
        driver = smtp
+       SMTP_DELIVERY
        APPLY_HEADER_CHANGES
+       DKIM_SIGN
        tls_require_ciphers = CONF_acceptable_ciphers
        tls_dh_min_bits = 508
        tls_tempfail_tryclear = true
@@ -442,6 +502,7 @@ m4_define(<:SMTP_TRANS_DHBITS:>,
        <:driver = smtp
        SMTP_DELIVERY
        APPLY_HEADER_CHANGES
+       DKIM_SIGN
        hosts_try_auth = *
        hosts_require_tls = DOMKV(tls-peer-ca, {*}{})
        hosts_require_auth = \
index 981f672..8f2db96 100644 (file)
--- a/config.m4
+++ b/config.m4
@@ -42,7 +42,7 @@ DEFCONF(admin_groups, root : adm)
 DEFCONF(trusted_groups, root : adm)
 
 ## Where the spam filter is.
-DEFCONF(spamd_address, 172.29.199.179)
+DEFCONF(spamd_address, 172.29.199.8)
 DEFCONF(spamd_port, 783)
 
 ## Default spam limit for incoming mail (multiplied by ten).
@@ -67,6 +67,7 @@ DEFCONF(sysconf_dir, /etc/mail)
 DEFCONF(userconf_dir, $home/.mail)
 DEFCONF(alias_file, /etc/aliases)
 DEFCONF(ca_dir, /etc/ca)
+DEFCONF(dkim_keys_dir, /var/lib/dkim-keys)
 
 ## User address suffix handling.
 DEFCONF(user_suffix_list, +* : -*)
@@ -74,9 +75,17 @@ DEFCONF(user_extaddr_fixup, ${sg {$local_part_suffix}{^[-+]}{}})
 
 ## Other hosts allowed to relay mail through us.
 DEFCONF(relay_clients, <m4_dnl
-; +trusted m4_dnl
+; +allnets m4_dnl
 ; 172.31.80.8 m4_dnl chiark (VPN)
-; 172.29.198.161 ; 2001:ba8:1d9:a000::1:1 m4_dnl national
+)
+
+## DKIM headers list.
+## Surprise!  Internal whitespace isn't allowed here.
+DEFCONF(dkim_headers, m4_dnl
+References : In-Reply-To : Subject : To : Date : Message-ID : m4_dnl
+From : Sender : Reply-To : Cc : m4_dnl
+Content-Transfer-Encoding : Content-Type : MIME-Version : m4_dnl
+Content-ID : Content-Description m4_dnl
 )
 
 ## TLS certificate list.
diff --git a/defs.m4 b/defs.m4
index f83e2e6..8beb3b1 100644 (file)
--- a/defs.m4
+++ b/defs.m4
@@ -142,6 +142,20 @@ CLRSEP<::>FOREACH(<:SEP(:)$:><:1:>, <:$1:>):>)
 m4_define(<:WARNING_HEADER:>,
 <:add_header = X-CONF_header_token-Warning: $1 $2:>)
 
+## _LOOKUP_ARGS([if-found], [if-not-found], [default])
+##
+## Processing for lookup arguments.  Use as
+##
+##     ${lookup {K} ST {F} _LOOKUP_ARGS(...)}$.
+##
+## IF-FOUND defaults to `{$value}'; IF-NOT-FOUND defaults to DEFAULT, which
+## defaults to `{}'.
+m4_define(<:_LOOKUP_ARGS:>,
+       <:m4_ifelse(<:$1$2$3:>, <::>, <::>,
+                   <:$1$2:>, <::>, <:{$value}$3:>,
+                   <:$2:>, <::>, <:$1$3:>,
+                   <:$1$2:>):>)
+
 ## LOOKUP_DOMAIN(dom, [if-found], [if-not-found])
 ##
 ## Look up DOM in the master domains file.  If it's found, put the
@@ -152,21 +166,20 @@ m4_define(<:WARNING_HEADER:>,
 m4_define(<:LOOKUP_DOMAIN:>,
        <:if exists{CONF_sysconf_dir/domains.conf} \
             {${lookup {$1}partial0-lsearch{CONF_sysconf_dir/domains.conf} \
-                      m4_ifelse(<:$2$3:>, <::>, <::>,
-                                <:$2:>, <::>, <:{$value}$3:>,
-                                <:$2$3:>)}} \
+                      _LOOKUP_ARGS(<:$2:>, <:$3:>)}} \
             $3:>)
 
-## KV(key, result)
+## KV(key, [result])
 ##
 ## Extract the value of the named KEY in the ${extract ...}-style string in
 ## the current $value (where it may have been left by `LOOKUP-DOMAIN').  The
 ## RESULT is a pair {IF-FOUND}{IF-NOT-FOUND}; the former may use $value to
 ## refer to the value; the latter may be `fail' (not in braces) to force
-## expansion failure.
+## expansion failure.  The default is to expand to the extracted value or
+## nothing.
 m4_define(<:KV:>, <:${extract {$1}{$value}$2}:>)
 
-## DOMKV(key, result)
+## DOMKV(key, [result])
 ##
 ## Extract the value of the named KEY in the ${extract ...}-style string in
 ## $domain_data; see `KV'.
index 88ae8c8..70623e6 100644 (file)
--- a/lists.m4
+++ b/lists.m4
@@ -27,21 +27,17 @@ hostlist localnet = <; \
        127.0.0.0/8 ; ::1
 hostlist thishost = <; \
         +localnet ; @[]
-hostlist foreign = <; \
-       212.13.198.76 ; 2001:ba8:0:1d9::1:1
+hostlist foreign = <;
 hostlist border = <; \
        ! +foreign ; \
-       62.49.204.144/28 ; 2001:470:1f09:1b98::/64 ; \
-       81.2.113.195 ; 81.187.238.128/28 ; \
-       212.13.198.64/28 ; 2001:ba8:0:1d9::/64
+       81.2.113.195 ; 81.187.238.128/28 ; 217.169.12.64/28 ; \
+       2001:8b0:c92:fff::/64
 hostlist trusted = <; \
        +localnet ; +border ; \
-       172.29.199.0/24 ; 2001:8b0:c92::/49 ; \
-       2001:ba8:1d9::/49 ; 2001:470:9740::/49
+       172.29.199.0/24 ; 2001:8b0:c92::/49
 hostlist allnets = <; \
        +localnet ; +border ; \
-       172.29.198.0/23 ; 2001:8b0:c92::/48 ; \
-       2001:ba8:1d9::/48 ; 2001:470:9740::/48
+       172.29.198.0/23 ; 2001:8b0:c92::/48
 
 ## Addresses which are likely to be bogus.  These are zero, loopback,
 ## multicast, broadcast (IPv4), and blocks reserved for documentation.
diff --git a/site.mk b/site.mk
new file mode 100644 (file)
index 0000000..332c1db
--- /dev/null
+++ b/site.mk
@@ -0,0 +1,13 @@
+### -*-makefile-*-
+
+HOSTS_hub              += stratocaster
+
+HOSTS_srv              += telecaster
+
+HOSTS_usersat          += jem artist jazz #vampire
+
+HOSTS_satellite                += gibson spirit #crybaby
+HOSTS_satellite                += ibanez radius roadstar universe
+HOSTS_satellite                += fender precision
+HOSTS_satellite                += eggle national
+HOSTS_satellite                += groove
diff --git a/spam.m4 b/spam.m4
index c51bc36..4570210 100644 (file)
--- a/spam.m4
+++ b/spam.m4
@@ -81,7 +81,7 @@ skip_spam_check:
        ## If the client is trusted, or this is a new submission, don't
        ## bother with any of this.  We will have verified the sender
        ## fairly aggressively before granting this level of trust.
-       accept   hosts = +trusted
+       accept   hosts = CONF_relay_clients
        accept   condition = ${if eq{$acl_c_mode}{submission}}
 
        ## If all domains have disabled spam checking then don't check.
index 700593b..e3e84c4 100644 (file)
@@ -28,8 +28,7 @@ FILTER_ROUTER(filter_vhost,
        VHOST
        condition = DOMKV(sysusers, {$value}{false})
        local_part_suffix = CONF_user_suffix_list
-       local_part_suffix_optional = true
-       :>)
+       local_part_suffix_optional = true:>)
 
 SECTION(routers, virtual-sysusers-spam)m4_dnl
 ## If we're letting general system users receive mail through this vhost then
@@ -38,6 +37,7 @@ USER_SPAMLIMIT_ROUTERS(<:vhost:>,
        <:CURRENT_LOCAL_PART:>, <:$domain:>, <:$sender_address:>,
        <:VHOST
        condition = DOMKV(sysusers, {$value}{false})
-       :>)
+:>)
 
+DIVERT(null)
 ###----- That's all, folks --------------------------------------------------