rbl: support informational-only RBL entries
Add DefaultInformationalRBLs (UCEPROTECT L2/L3) and track listings separately via RelevantListedCount so these broader lists are displayed but excluded from the deliverability score calculation.
This commit is contained in:
parent
3cc39c9c54
commit
28424729a5
1 changed files with 38 additions and 15 deletions
|
|
@ -38,6 +38,7 @@ type RBLChecker struct {
|
||||||
RBLs []string
|
RBLs []string
|
||||||
CheckAllIPs bool // Check all IPs found in headers, not just the first one
|
CheckAllIPs bool // Check all IPs found in headers, not just the first one
|
||||||
resolver *net.Resolver
|
resolver *net.Resolver
|
||||||
|
informationalSet map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultRBLs is a list of commonly used RBL providers
|
// DefaultRBLs is a list of commonly used RBL providers
|
||||||
|
|
@ -48,6 +49,8 @@ var DefaultRBLs = []string{
|
||||||
"b.barracudacentral.org", // Barracuda
|
"b.barracudacentral.org", // Barracuda
|
||||||
"cbl.abuseat.org", // CBL (Composite Blocking List)
|
"cbl.abuseat.org", // CBL (Composite Blocking List)
|
||||||
"dnsbl-1.uceprotect.net", // UCEPROTECT Level 1
|
"dnsbl-1.uceprotect.net", // UCEPROTECT Level 1
|
||||||
|
"dnsbl-2.uceprotect.net", // UCEPROTECT Level 2 (informational)
|
||||||
|
"dnsbl-3.uceprotect.net", // UCEPROTECT Level 3 (informational)
|
||||||
"spam.spamrats.com", // SpamRats SPAM
|
"spam.spamrats.com", // SpamRats SPAM
|
||||||
"dyna.spamrats.com", // SpamRats dynamic IPs
|
"dyna.spamrats.com", // SpamRats dynamic IPs
|
||||||
"psbl.surriel.com", // PSBL
|
"psbl.surriel.com", // PSBL
|
||||||
|
|
@ -58,6 +61,13 @@ var DefaultRBLs = []string{
|
||||||
"bl.nszones.com", // NSZones
|
"bl.nszones.com", // NSZones
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultInformationalRBLs lists RBLs that are checked but not counted in the score.
|
||||||
|
// These are typically broader lists where being listed is less definitive.
|
||||||
|
var DefaultInformationalRBLs = []string{
|
||||||
|
"dnsbl-2.uceprotect.net", // UCEPROTECT Level 2: entire netblocks, may cause false positives
|
||||||
|
"dnsbl-3.uceprotect.net", // UCEPROTECT Level 3: entire ASes, too broad for scoring
|
||||||
|
}
|
||||||
|
|
||||||
// NewRBLChecker creates a new RBL checker with configurable timeout and RBL list
|
// NewRBLChecker creates a new RBL checker with configurable timeout and RBL list
|
||||||
func NewRBLChecker(timeout time.Duration, rbls []string, checkAllIPs bool) *RBLChecker {
|
func NewRBLChecker(timeout time.Duration, rbls []string, checkAllIPs bool) *RBLChecker {
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
|
|
@ -66,13 +76,16 @@ func NewRBLChecker(timeout time.Duration, rbls []string, checkAllIPs bool) *RBLC
|
||||||
if len(rbls) == 0 {
|
if len(rbls) == 0 {
|
||||||
rbls = DefaultRBLs
|
rbls = DefaultRBLs
|
||||||
}
|
}
|
||||||
|
informationalSet := make(map[string]bool, len(DefaultInformationalRBLs))
|
||||||
|
for _, rbl := range DefaultInformationalRBLs {
|
||||||
|
informationalSet[rbl] = true
|
||||||
|
}
|
||||||
return &RBLChecker{
|
return &RBLChecker{
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
RBLs: rbls,
|
RBLs: rbls,
|
||||||
CheckAllIPs: checkAllIPs,
|
CheckAllIPs: checkAllIPs,
|
||||||
resolver: &net.Resolver{
|
resolver: &net.Resolver{PreferGo: true},
|
||||||
PreferGo: true,
|
informationalSet: informationalSet,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +93,8 @@ func NewRBLChecker(timeout time.Duration, rbls []string, checkAllIPs bool) *RBLC
|
||||||
type RBLResults struct {
|
type RBLResults struct {
|
||||||
Checks map[string][]api.BlacklistCheck // Map of IP -> list of RBL checks for that IP
|
Checks map[string][]api.BlacklistCheck // Map of IP -> list of RBL checks for that IP
|
||||||
IPsChecked []string
|
IPsChecked []string
|
||||||
ListedCount int
|
ListedCount int // Total listings including informational RBLs
|
||||||
|
RelevantListedCount int // Listings on scoring (non-informational) RBLs only
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckEmail checks all IPs found in the email headers against RBLs
|
// CheckEmail checks all IPs found in the email headers against RBLs
|
||||||
|
|
@ -104,6 +118,9 @@ func (r *RBLChecker) CheckEmail(email *EmailMessage) *RBLResults {
|
||||||
results.Checks[ip] = append(results.Checks[ip], check)
|
results.Checks[ip] = append(results.Checks[ip], check)
|
||||||
if check.Listed {
|
if check.Listed {
|
||||||
results.ListedCount++
|
results.ListedCount++
|
||||||
|
if !r.informationalSet[rbl] {
|
||||||
|
results.RelevantListedCount++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,14 +293,20 @@ func (r *RBLChecker) reverseIP(ipStr string) string {
|
||||||
return fmt.Sprintf("%d.%d.%d.%d", ipv4[3], ipv4[2], ipv4[1], ipv4[0])
|
return fmt.Sprintf("%d.%d.%d.%d", ipv4[3], ipv4[2], ipv4[1], ipv4[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateRBLScore calculates the blacklist contribution to deliverability
|
// CalculateRBLScore calculates the blacklist contribution to deliverability.
|
||||||
|
// Informational RBLs are not counted in the score.
|
||||||
func (r *RBLChecker) CalculateRBLScore(results *RBLResults) (int, string) {
|
func (r *RBLChecker) CalculateRBLScore(results *RBLResults) (int, string) {
|
||||||
if results == nil || len(results.IPsChecked) == 0 {
|
if results == nil || len(results.IPsChecked) == 0 {
|
||||||
// No IPs to check, give benefit of doubt
|
// No IPs to check, give benefit of doubt
|
||||||
return 100, ""
|
return 100, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
percentage := 100 - results.ListedCount*100/len(r.RBLs)
|
scoringRBLCount := len(r.RBLs) - len(r.informationalSet)
|
||||||
|
if scoringRBLCount <= 0 {
|
||||||
|
return 100, "A+"
|
||||||
|
}
|
||||||
|
|
||||||
|
percentage := 100 - results.RelevantListedCount*100/scoringRBLCount
|
||||||
return percentage, ScoreToGrade(percentage)
|
return percentage, ScoreToGrade(percentage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue