spam.m4, user-spam.m4: Log details about spam rejections for users.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 23 Mar 2016 22:33:05 +0000 (22:33 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 23 Mar 2016 22:33:05 +0000 (22:33 +0000)
  * When we notice a delivery to a user during recipient verification,
    take a note of the user's name in the `user' field of the
    address_data.

  * In the `rcpt_spam' ACL, pick the user name out of the address_data
    and remember it and the corresponding recipient address (in a rather
    unpleasantly escaped form) along with the others in the variable
    `$acl_m_spam_users'.

  * Finally, in `data_spam', if we end up rejecting the message, log a
    message with the condensed SpamAssassin report, and the user names
    and matching recipient addresses.

This leaves, in the rejectlog, enough information for a service to tell
which rejection reports apply to a calling user, and tell them about the
message.  We should be able to pick the sender address and the headers
from the usual rejection report, but we don't want to leak the other
envelope recipient addresses.  (The user would have seen the /header/
recipients had we not rejected the message as being spam; but the
envelope may contain Bcc recipients or other interesting secrets.)

spam.m4
user-spam.m4

diff --git a/spam.m4 b/spam.m4
index 897e6a9..7ed0f04 100644 (file)
--- a/spam.m4
+++ b/spam.m4
@@ -49,12 +49,13 @@ m4_define(<:SPAMLIMIT_ROUTER:>,
 m4_define(<:SPAMLIMIT_SET:>,
        <:address_data = \
                ${if def:address_data {$address_data}{}} \
-               $1:>)
+               m4_ifelse(<:$2:>, <::>, <::>, <:$2 \
+               :>)$1:>)
 
 m4_define(<:SPAMLIMIT_LOOKUP:>,
        <:condition = ${if exists{$1}}
        SPAMLIMIT_SET(<:${lookup {$2@$3/$4} nwildlsearch {$1} \
-                              {SPAMLIMIT_CHECK($value)}}:>):>)
+                              {SPAMLIMIT_CHECK(<:$value:>)}}:>, <:$5:>):>)
 
 m4_define(<:SPAMLIMIT_USERV:>,
        <:SPAMLIMIT_SET(<:${run {/usr/bin/timeout 5s \
@@ -62,7 +63,7 @@ m4_define(<:SPAMLIMIT_USERV:>,
                                        SHQUOTE($1) exim-spam-limit \
                                        SHQUOTE($4) \
                                        SHQUOTE($2) SHQUOTE(@$3)} \
-                               {SPAMLIMIT_CHECK($value)}}:>):>)
+                               {SPAMLIMIT_CHECK(<:$value:>)}}:>, <:$5:>):>)
 
 m4_define(<:GET_ADDRDATA:>,
        <:extract{<:$1:>}{${if def:address_data{$address_data}{}}}:>)
@@ -119,6 +120,14 @@ rcpt_spam:
                        ${sg {${GET_ADDRDATA(spam_limit){$value}{nil}}} \
                             {^(|.*\\D.*)\$}{CONF_spam_max}}
 
+       warn     condition = ${GET_ADDRDATA(user){true}{false}}
+                set acl_m_spam_users = \
+                       ${if def:acl_m_spam_users {$acl_m_spam_users::}{}}\
+                       ${GET_ADDRDATA(user) \
+                               {$value=${sg{$local_part@$domain}\
+                                           {([!:])}{!\$1}}} \
+                               fail}
+
        ## If there's a spam limit already established, and it's different
        ## from this user's limit, then the sender will have to try this user
        ## again later.
@@ -200,8 +209,15 @@ data_spam:
                 ## Undo the escaping.
                 set acl_m_spam_tests = ${sg{$acl_m_spam_tests}{!(.)}{\$1}}
 
-       ## If we've decided to reject, then tell the sender to get knotted.
+       ## If we've decided to reject, then leave a dropping in the log file
+       ## so that users can analyse rejections for incoming messages, and
+       ## tell the sender to get knotted.
        deny     message = Tinned meat product detected ($spam_score)
+                log_message = Spam rejection \
+                       score=$spam_score \
+                       limit=$acl_m_spam_limit_presentation \
+                       tests=$acl_m_spam_tests \
+                       users=$acl_m_spam_users
                 condition = ${if >{$spam_score_int}{$acl_m_spam_limit} \
                                  {true}{false}}
 
index 1b47f15..42b6f15 100644 (file)
@@ -35,10 +35,10 @@ m4_define(<:USER_SPAMLIMIT_ROUTER:>,
 m4_define(<:USER_SPAMLIMIT_ROUTERS:>,
 <:USER_SPAMLIMIT_ROUTER(<:lookup:>, <:$1:>,
        <:$5SPAMLIMIT_LOOKUP(<:CONF_userconf_dir/spam-limit:>,
-               <:$2:>, <:$3:>, <:$4:>):>)
+               <:$2:>, <:$3:>, <:$4:>, <:user=$local_part:>):>)
 USER_SPAMLIMIT_ROUTER(<:userv:>, <:$1:>,
        <:$5SPAMLIMIT_USERV(<:SHQUOTE($local_part):>,
-               <:$2:>, <:$3:>, <:$4:>):>):>)
+               <:$2:>, <:$3:>, <:$4:>, <:user=$local_part:>):>):>)
 
 m4_define(<:CURRENT_LOCAL_PART:>,
        <:$local_part_prefix$local_part$local_part_suffix:>)