Skip to content

[WIP] Refactor DNSBL response handling for detailed severity#2

Closed
Copilot wants to merge 1 commit intomasterfrom
copilot/update-dnsbl-response-handling
Closed

[WIP] Refactor DNSBL response handling for detailed severity#2
Copilot wants to merge 1 commit intomasterfrom
copilot/update-dnsbl-response-handling

Conversation

Copy link
Copy Markdown

Copilot AI commented Jan 10, 2026

Thanks for asking me to work on this. I will get started on it and keep this PR's description up to date as I form a plan and make progress.

Original prompt

Problem

Currently, Maddy's DNSBL implementation treats all response codes within a configured range equally. When using combined DNSBLs like Spamhaus ZEN (zen.spamhaus.org), different return codes indicate different listing types with different severity:

  • 127.0.0.2, 127.0.0.3 → SBL (known spam sources) - high severity
  • 127.0.0.4-127.0.0.7 → XBL (exploited/compromised hosts) - high severity
  • 127.0.0.10, 127.0.0.11 → PBL (policy block, dynamic IPs) - lower severity

Currently, Maddy:

  1. Counts any response within 127.0.0.1/24 as a single "hit"
  2. Applies the same score regardless of which specific code was returned
  3. Cannot provide response-code-specific rejection messages
  4. If multiple codes are returned (e.g., 127.0.0.3, 127.0.0.11, 127.0.0.4), they all count as one hit with one score

This means users who want different scores for different listing types must query separate lists (sbl.spamhaus.org, xbl.spamhaus.org, pbl.spamhaus.org), resulting in 3 DNS queries instead of 1.

Reference: https://docs.spamhaus.com/datasets/docs/source/40-real-world-usage/PublicMirrors/MTAs/020-Postfix.html

Proposed Solution

Add a new response configuration block that allows per-response-code scoring and custom messages:

check.dnsbl {
    reject_threshold 10
    quarantine_threshold 5

    zen.spamhaus.org {
        client_ipv4 yes
        client_ipv6 yes
        
        # SBL - Spamhaus Block List (known spam sources)
        response 127.0.0.2 127.0.0.3 {
            score 10
            message "Listed in Spamhaus SBL. See https://check.spamhaus.org/"
        }
        
        # XBL - Exploits Block List (compromised hosts)
        response 127.0.0.4 127.0.0.5 127.0.0.6 127.0.0.7 {
            score 10
            message "Listed in Spamhaus XBL. See https://check.spamhaus.org/"
        }
        
        # PBL - Policy Block List (dynamic IPs)
        response 127.0.0.10 127.0.0.11 {
            score 5
            message "Listed in Spamhaus PBL. See https://check.spamhaus.org/"
        }
    }
}

Implementation Details

1. Add new ResponseRule struct in internal/check/dnsbl/dnsbl.go:

type ResponseRule struct {
    Networks []net.IPNet
    Score    int
    Message  string // Custom rejection/quarantine message
}

2. Update List struct to include ResponseRules:

type List struct {
    Zone string
    ClientIPv4 bool
    ClientIPv6 bool
    EHLO     bool
    MAILFROM bool
    
    // Legacy: flat score for any response (used when ResponseRules is empty)
    ScoreAdj  int
    Responses []net.IPNet

    // New: per-response-code rules
    ResponseRules []ResponseRule
}

3. Update ListedErr in internal/check/dnsbl/common.go to include score and message:

type ListedErr struct {
    Identity string
    List     string
    Reason   string
    Score    int    // Score from matched response rule
    Message  string // Custom message from matched response rule
}

4. Update checkIP function in internal/check/dnsbl/common.go to handle ResponseRules:

  • If ResponseRules is configured, match each returned IP against rules and sum scores
  • If only legacy Responses is configured, use existing behavior for backwards compatibility
  • Split into checkIPWithRules (new) and checkIPLegacy (existing behavior)

5. Update checkLists in internal/check/dnsbl/dnsbl.go to use score from ListedErr:

  • Use ListedErr.Score when set (new behavior)
  • Fall back to list.ScoreAdj for backwards compatibility
  • Collect custom messages for rejection responses

6. Update readListCfg in internal/check/dnsbl/dnsbl.go to parse new response blocks:

  • Add parseResponseRule function to handle the new syntax
  • Support both CIDR notation and plain IPs in response arguments

7. Update documentation in docs/reference/checks/dnsbl.md

Files to Modify

  1. internal/check/dnsbl/dnsbl.go - Add ResponseRule struct, update List struct, update checkLists, update readListCfg, add parseResponseRule
  2. internal/check/dnsbl/common.go - Update ListedErr, update checkIP to handle ResponseRules
  3. internal/check/dnsbl/dnsbl_test.go - Add test cases for new functionality
  4. docs/reference/checks/dnsbl.md - Document the new response block syntax

Benefits

Feature Before After
DNS queries for ZEN 1 (but codes ignored) 1 (codes interpreted)
Per-code scoring
Custom rejection messages
Backwards compatible N/A ✅ (legacy responses + score still works)

Test Cases to Add

  1. Multiple return codes from single DNSBL with different scores → scores should sum
  2. Only low-severity code returned → quarantine but not reject
  3. High-severity code returned → reject
  4. Legacy configuration without response blocks → existing behavior preserved
  5. Response code not matching any rule → not counted

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI requested a review from thisisjaymehta January 10, 2026 12:52
Copilot stopped work on behalf of thisisjaymehta due to an error January 10, 2026 12:52
@thisisjaymehta thisisjaymehta marked this pull request as ready for review January 10, 2026 13:03
@thisisjaymehta thisisjaymehta deleted the copilot/update-dnsbl-response-handling branch January 10, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants