Skip to content

Commit 66339dd

Browse files
Address upstream PR feedback: use cfg.Callback, extract function, remove version annotation
Co-authored-by: thisisjaymehta <31812582+thisisjaymehta@users.noreply.github.com>
1 parent 11be667 commit 66339dd

3 files changed

Lines changed: 79 additions & 92 deletions

File tree

docs/reference/checks/dnsbl.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,6 @@ rules is used instead of this flat score value.
203203

204204
### response _ip..._
205205

206-
**New in 0.8**
207-
208206
Defines per-response-code rules for scoring and custom messages. This is useful
209207
for combined DNSBLs like Spamhaus ZEN that return different codes for different
210208
listing types.

internal/check/dnsbl/common.go

Lines changed: 68 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,34 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, cfg List, domain st
9494
}
9595
}
9696

97+
func matchResponseRules(addrs []net.IPAddr, rules []ResponseRule) (score int, messages []string, reasons []string, matched bool) {
98+
// Track which rules have been matched to avoid counting the same rule multiple times
99+
matchedRules := make(map[int]bool)
100+
101+
for _, addr := range addrs {
102+
for ruleIdx, rule := range rules {
103+
// Skip if this rule has already been matched
104+
if matchedRules[ruleIdx] {
105+
continue
106+
}
107+
108+
for _, respNet := range rule.Networks {
109+
if respNet.Contains(addr.IP) {
110+
score += rule.Score
111+
if rule.Message != "" {
112+
messages = append(messages, rule.Message)
113+
}
114+
reasons = append(reasons, addr.IP.String())
115+
matchedRules[ruleIdx] = true
116+
matched = true
117+
break // Move to next rule
118+
}
119+
}
120+
}
121+
}
122+
return
123+
}
124+
97125
func checkIP(ctx context.Context, resolver dns.Resolver, cfg List, ip net.IP) error {
98126
ipv6 := true
99127
if ipv4 := ip.To4(); ipv4 != nil {
@@ -119,111 +147,72 @@ func checkIP(ctx context.Context, resolver dns.Resolver, cfg List, ip net.IP) er
119147
return err
120148
}
121149

150+
var filteredAddrs []net.IPAddr
151+
var score int
152+
var customMessage string
153+
122154
// If ResponseRules is configured, use new behavior
123155
if len(cfg.ResponseRules) > 0 {
124-
totalScore := 0
125-
var matchedMessages []string
126-
var matchedReasons []string
127-
128-
// Track which rules have been matched to avoid counting the same rule multiple times
129-
matchedRules := make(map[int]bool)
130-
131-
for _, addr := range addrs {
132-
for ruleIdx, rule := range cfg.ResponseRules {
133-
// Skip if this rule has already been matched
134-
if matchedRules[ruleIdx] {
135-
continue
136-
}
137-
138-
for _, respNet := range rule.Networks {
139-
if respNet.Contains(addr.IP) {
140-
totalScore += rule.Score
141-
if rule.Message != "" {
142-
matchedMessages = append(matchedMessages, rule.Message)
143-
}
144-
matchedReasons = append(matchedReasons, addr.IP.String())
145-
matchedRules[ruleIdx] = true
146-
break // Move to next rule
147-
}
148-
}
149-
}
150-
}
151-
152-
if totalScore == 0 {
156+
matchedScore, matchedMessages, matchedReasons, matched := matchResponseRules(addrs, cfg.ResponseRules)
157+
if !matched {
153158
return nil
154159
}
155-
156-
// Attempt to extract explanation string from TXT records
157-
txts, err := resolver.LookupTXT(ctx, query)
158-
var reason string
159-
if err == nil && len(txts) > 0 {
160-
reason = strings.Join(txts, "; ")
161-
} else {
162-
reason = strings.Join(matchedReasons, "; ")
163-
}
164-
160+
score = matchedScore
161+
165162
// Use first matched message if available
166-
message := ""
167163
if len(matchedMessages) > 0 {
168-
message = matchedMessages[0]
164+
customMessage = matchedMessages[0]
169165
}
170-
171-
return ListedErr{
172-
Identity: ip.String(),
173-
List: cfg.Zone,
174-
Reason: reason,
175-
Score: totalScore,
176-
Message: message,
166+
167+
// Build filteredAddrs from matched reasons for TXT lookup fallback
168+
for _, reason := range matchedReasons {
169+
filteredAddrs = append(filteredAddrs, net.IPAddr{IP: net.ParseIP(reason)})
177170
}
178-
}
179-
180-
// Legacy behavior: use flat Responses filter
181-
filteredAddrs := make([]net.IPAddr, 0, len(addrs))
182-
addrsLoop:
183-
for _, addr := range addrs {
184-
// No responses whitelist configured - permit all.
185-
if len(cfg.Responses) == 0 {
186-
filteredAddrs = append(filteredAddrs, addr)
187-
continue
188-
}
189-
190-
for _, respNet := range cfg.Responses {
191-
if respNet.Contains(addr.IP) {
171+
} else {
172+
// Legacy behavior: use flat Responses filter
173+
filteredAddrs = make([]net.IPAddr, 0, len(addrs))
174+
addrsLoop:
175+
for _, addr := range addrs {
176+
// No responses whitelist configured - permit all.
177+
if len(cfg.Responses) == 0 {
192178
filteredAddrs = append(filteredAddrs, addr)
193-
continue addrsLoop
179+
continue
180+
}
181+
182+
for _, respNet := range cfg.Responses {
183+
if respNet.Contains(addr.IP) {
184+
filteredAddrs = append(filteredAddrs, addr)
185+
continue addrsLoop
186+
}
194187
}
195188
}
196-
}
197189

198-
if len(filteredAddrs) == 0 {
199-
return nil
190+
if len(filteredAddrs) == 0 {
191+
return nil
192+
}
200193
}
201194

202-
// Attempt to extract explanation string.
195+
// Attempt to extract explanation string from TXT records (shared by both paths)
203196
txts, err := resolver.LookupTXT(ctx, query)
204-
if err != nil || len(txts) == 0 {
197+
var reason string
198+
if err == nil && len(txts) > 0 {
199+
reason = strings.Join(txts, "; ")
200+
} else {
205201
// Not significant, include addresses as reason. Usually they are
206202
// mapped to some predefined 'reasons' by BL.
207-
208203
reasonParts := make([]string, 0, len(filteredAddrs))
209204
for _, addr := range filteredAddrs {
210205
reasonParts = append(reasonParts, addr.IP.String())
211206
}
212-
213-
return ListedErr{
214-
Identity: ip.String(),
215-
List: cfg.Zone,
216-
Reason: strings.Join(reasonParts, "; "),
217-
}
207+
reason = strings.Join(reasonParts, "; ")
218208
}
219209

220-
// Some BLs provide multiple reasons (meta-BLs such as Spamhaus Zen) so
221-
// don't mangle them by joining with "", instead join with "; ".
222-
223210
return ListedErr{
224211
Identity: ip.String(),
225212
List: cfg.Zone,
226-
Reason: strings.Join(txts, "; "),
213+
Reason: reason,
214+
Score: score,
215+
Message: customMessage,
227216
}
228217
}
229218

internal/check/dnsbl/dnsbl.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ func (bl *DNSBL) readListCfg(node config.Node) error {
134134
cfg.Bool("mailfrom", false, defaultBL.EHLO, &listCfg.MAILFROM)
135135
cfg.Int("score", false, false, 1, &listCfg.ScoreAdj)
136136
cfg.StringList("responses", false, false, []string{"127.0.0.1/24"}, &responseNets)
137+
cfg.Callback("response", func(_ *config.Map, node config.Node) error {
138+
rule, err := parseResponseRule(node)
139+
if err != nil {
140+
return err
141+
}
142+
listCfg.ResponseRules = append(listCfg.ResponseRules, rule)
143+
return nil
144+
})
137145
if _, err := cfg.Process(); err != nil {
138146
return err
139147
}
@@ -152,17 +160,9 @@ func (bl *DNSBL) readListCfg(node config.Node) error {
152160
listCfg.Responses = append(listCfg.Responses, *ipNet)
153161
}
154162

155-
// Parse response blocks from node children
156-
for _, child := range node.Children {
157-
if child.Name == "response" {
158-
rule, err := parseResponseRule(child)
159-
if err != nil {
160-
return err
161-
}
162-
listCfg.ResponseRules = append(listCfg.ResponseRules, rule)
163-
} else {
164-
return config.NodeErr(child, "unknown directive: %s", child.Name)
165-
}
163+
// Warn if both response and responses are configured
164+
if len(listCfg.ResponseRules) > 0 && len(responseNets) > 0 {
165+
bl.log.Msg("both 'response' blocks and 'responses' directive are specified, 'response' blocks take precedence", "list", node.Name)
166166
}
167167

168168
for _, zone := range append([]string{node.Name}, node.Args...) {

0 commit comments

Comments
 (0)