Rspamd - Filter for Google Groups Spam

Discussion in 'Tips/Tricks/Mods' started by pyte, Oct 10, 2025 at 9:15 AM.

  1. pyte

    pyte Well-Known Member HowtoForge Supporter

    Explanation of the Problem

    We received a lot of spam from Google Groups in the past year due to technical design of Google Groups, spammers have an easy option to distribute large amount of spam from valid Google mail servers. There are a few key issues with Google Groups:
    • Victims don't need to opt-in for Google Groups. Spammers can just add E-Mailadresses as they like
    • Unsubscribing is not possible, because in most cases the spammers set the group to private which makes unsubscribing impossible
    • Often times they add alot of auto-response mailsystems to these groups which results in additional unwanted mail because these system answer the initial spam mail, and the answer gets then distributed to all members/victims of the group again
    When I was analyzing the messages I saw that a few big companies used Google Groups rather legitimatly to send out there newsletters or weekly offers with the help of Google Groups. With that in mind simply detecting if a mail originates from a Google Groups and blocking it would not be a solution without colleteral damage.

    I came up with the following solution to the issue:

    • Create a custom lua plugin for rspamd to detect mails originating from google groups and append a custom symbol
    • Use composite rules to decide if the mail is spam or not

    Technical configuration

    First I created a custom lua plugin in /etc/rspamd/plugins.d/kits_header_google_group.lua:
    Code:
    rspamd_config:register_symbol{
        name = "KITS_HEADER_GOOGLE_GROUP",
        score = 0.1,
        group = "headers",
        description = "Message contains X-Google-Group-Id header or List-Unsubscribe header with googlegroups",
        callback = function(task)
          -- Check for X-Google-Group-Id header
          if task:get_header('X-Google-Group-Id') then
            return true
          end
          
          -- Check for List-Unsubscribe header containing 'googlegroups'
          local list_unsubscribe = task:get_header('List-Unsubscribe')
          if list_unsubscribe and string.find(list_unsubscribe:lower(), 'googlegroups') then
            return true
          end
          
          return false
        end
    }

    After that you need to register a module with the same name in /etc/rspamd/modules.d/kits_header_google_group.conf:
    Code:
    # Empty config to enable lua plugin
    kits_header_google_group { }
    This will now tag every mail that is originating from a Google Group with KITS_HEADER_GOOGLE_GROUP and score it with 0.1. You can change the naming scheme however you like, but make sure to change the config of the module and maybe the file accordingly as well.

    In the last step I created two composite rules that then evaluate if the message really is spam or not. For this I created these two rules in the /etc/rspamd/override.d/composite.conf:

    Code:
    # Google Group origin with bulk or freemail origin
    KITS_GOOGLE_GROUP_BAD {
      expression = "KITS_HEADER_GOOGLE_GROUP and (DCC_REJECT | FUZZY_BULK | FREEMAIL_FROM)";
      score = 8.0;
    }
    
    # Google Group origin with bulk and freemail origin
    KITS_GOOGLE_GROUP_WORST {
      expression = "KITS_HEADER_GOOGLE_GROUP and DCC_REJECT and FUZZY_BULK and FREEMAIL_FROM";
      score = 20.0;
    }
    
    This will now check if the message is a Google Groups message and if the symbols DCC_REJECT, FUZZY_BULK or FREEMAIL_FROM are present as well it will add a score of 8 to the message.

    The naming scheme, scores and composite rules are specific to my setup. You should always set scores that match your specific configuration and always check the configuration after making changes with "rspamadm configtest".

    Testing and Troubleshooting

    As already mentioned you should always check the configuration with:
    Code:
    rspamadm configtest
    You can check if the composite rules is loaded correctly with:
    Code:
    rspamadm configdump | grep KITS_HEADER_GOOGLE_GROUP
    You can test if the rule works with a simple test:
    Code:
    rspamc -i 1.2.3.4 -p < test.eml
    The test.eml:
    Code:
    Return-Path: <[email protected]>
    Received: from localhost (localhost [127.0.0.1])
        by example.com (Postfix) with ESMTP id 123456789
        for <[email protected]>; Thu, 1 Jan 2025 12:34:56 +0000 (UTC)
    From: Sender Name <[email protected]>
    To: Recipient Name <[email protected]>
    Subject: Test email
    Date: Thu, 1 Jan 2025 12:34:56 +0000
    Message-ID: <[email protected]>
    List-Help: <https://support.google.com/a/zf.thesparklebar.com/bin/topic.py?topic=25838>,
     <mailto:[email protected]>
    List-Subscribe: <https://groups.google.com/a/ek.shirleyaraujo.com.br/group/supposddsdrt/subscribe>,
     <mailto:[email protected]>
    List-Unsubscribe: <mailto:[email protected]>,
     <https://groups.google.com/a/zf.thesparklebar.com/group/od/subscribe>
    MIME-Version: 1.0
    Content-Type: text/plain; charset="UTF-8"
    Content-Transfer-Encoding: 7bit
    
    Hello,
    
    This is a properly formatted test email for rspamd scanning.
    
    Regards,
    Tester
    
     
    Mark P and Th0m like this.

Share This Page