Compare commits
2 commits
2d3316eaaf
...
871f4e62f6
| Author | SHA1 | Date | |
|---|---|---|---|
| 871f4e62f6 | |||
| 86ec7a6100 |
8 changed files with 36 additions and 24 deletions
|
|
@ -37,6 +37,7 @@ func declareFlags(o *Config) {
|
||||||
flag.DurationVar(&o.Analysis.DNSTimeout, "dns-timeout", o.Analysis.DNSTimeout, "Timeout when performing DNS query")
|
flag.DurationVar(&o.Analysis.DNSTimeout, "dns-timeout", o.Analysis.DNSTimeout, "Timeout when performing DNS query")
|
||||||
flag.DurationVar(&o.Analysis.HTTPTimeout, "http-timeout", o.Analysis.HTTPTimeout, "Timeout when performing HTTP query")
|
flag.DurationVar(&o.Analysis.HTTPTimeout, "http-timeout", o.Analysis.HTTPTimeout, "Timeout when performing HTTP query")
|
||||||
flag.Var(&StringArray{&o.Analysis.RBLs}, "rbl", "Append a RBL (use this option multiple time to append multiple RBLs)")
|
flag.Var(&StringArray{&o.Analysis.RBLs}, "rbl", "Append a RBL (use this option multiple time to append multiple RBLs)")
|
||||||
|
flag.BoolVar(&o.Analysis.CheckAllIPs, "check-all-ips", o.Analysis.CheckAllIPs, "Check all IPs found in email headers against RBLs (not just the first one)")
|
||||||
flag.DurationVar(&o.ReportRetention, "report-retention", o.ReportRetention, "How long to keep reports (e.g., 720h, 30d). 0 = keep forever")
|
flag.DurationVar(&o.ReportRetention, "report-retention", o.ReportRetention, "How long to keep reports (e.g., 720h, 30d). 0 = keep forever")
|
||||||
flag.UintVar(&o.RateLimit, "rate-limit", o.RateLimit, "API rate limit (requests per second per IP)")
|
flag.UintVar(&o.RateLimit, "rate-limit", o.RateLimit, "API rate limit (requests per second per IP)")
|
||||||
flag.Var(&URL{&o.SurveyURL}, "survey-url", "URL for user feedback survey")
|
flag.Var(&URL{&o.SurveyURL}, "survey-url", "URL for user feedback survey")
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,10 @@ type EmailConfig struct {
|
||||||
|
|
||||||
// AnalysisConfig contains timeout and behavior settings for email analysis
|
// AnalysisConfig contains timeout and behavior settings for email analysis
|
||||||
type AnalysisConfig struct {
|
type AnalysisConfig struct {
|
||||||
DNSTimeout time.Duration
|
DNSTimeout time.Duration
|
||||||
HTTPTimeout time.Duration
|
HTTPTimeout time.Duration
|
||||||
RBLs []string
|
RBLs []string
|
||||||
|
CheckAllIPs bool // Check all IPs found in headers, not just the first one
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns a configuration with sensible defaults
|
// DefaultConfig returns a configuration with sensible defaults
|
||||||
|
|
@ -86,6 +87,7 @@ func DefaultConfig() *Config {
|
||||||
DNSTimeout: 5 * time.Second,
|
DNSTimeout: 5 * time.Second,
|
||||||
HTTPTimeout: 10 * time.Second,
|
HTTPTimeout: 10 * time.Second,
|
||||||
RBLs: []string{},
|
RBLs: []string{},
|
||||||
|
CheckAllIPs: false, // By default, only check the first IP
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ func NewEmailAnalyzer(cfg *config.Config) *EmailAnalyzer {
|
||||||
cfg.Analysis.DNSTimeout,
|
cfg.Analysis.DNSTimeout,
|
||||||
cfg.Analysis.HTTPTimeout,
|
cfg.Analysis.HTTPTimeout,
|
||||||
cfg.Analysis.RBLs,
|
cfg.Analysis.RBLs,
|
||||||
|
cfg.Analysis.CheckAllIPs,
|
||||||
)
|
)
|
||||||
|
|
||||||
return &EmailAnalyzer{
|
return &EmailAnalyzer{
|
||||||
|
|
|
||||||
|
|
@ -751,7 +751,7 @@ func (c *ContentAnalyzer) CalculateContentScore(results *ContentResults) (int, s
|
||||||
brokenLinks++
|
brokenLinks++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
score += 20 * brokenLinks / len(results.Links)
|
score += 20 * (len(results.Links) - brokenLinks) / len(results.Links)
|
||||||
// Too much links, 10 points penalty
|
// Too much links, 10 points penalty
|
||||||
if len(results.Links) > 30 {
|
if len(results.Links) > 30 {
|
||||||
score -= 10
|
score -= 10
|
||||||
|
|
@ -769,7 +769,7 @@ func (c *ContentAnalyzer) CalculateContentScore(results *ContentResults) (int, s
|
||||||
noAltCount++
|
noAltCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
score += 15 * noAltCount / len(results.Images)
|
score += 15 * (len(results.Images) - noAltCount) / len(results.Images)
|
||||||
} else {
|
} else {
|
||||||
// No images is Ok
|
// No images is Ok
|
||||||
score += 15
|
score += 15
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,10 @@ import (
|
||||||
|
|
||||||
// RBLChecker checks IP addresses against DNS-based blacklists
|
// RBLChecker checks IP addresses against DNS-based blacklists
|
||||||
type RBLChecker struct {
|
type RBLChecker struct {
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
RBLs []string
|
RBLs []string
|
||||||
resolver *net.Resolver
|
CheckAllIPs bool // Check all IPs found in headers, not just the first one
|
||||||
|
resolver *net.Resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultRBLs is a list of commonly used RBL providers
|
// DefaultRBLs is a list of commonly used RBL providers
|
||||||
|
|
@ -50,7 +51,7 @@ var DefaultRBLs = []string{
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) *RBLChecker {
|
func NewRBLChecker(timeout time.Duration, rbls []string, checkAllIPs bool) *RBLChecker {
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
timeout = 5 * time.Second // Default timeout
|
timeout = 5 * time.Second // Default timeout
|
||||||
}
|
}
|
||||||
|
|
@ -58,8 +59,9 @@ func NewRBLChecker(timeout time.Duration, rbls []string) *RBLChecker {
|
||||||
rbls = DefaultRBLs
|
rbls = DefaultRBLs
|
||||||
}
|
}
|
||||||
return &RBLChecker{
|
return &RBLChecker{
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
RBLs: rbls,
|
RBLs: rbls,
|
||||||
|
CheckAllIPs: checkAllIPs,
|
||||||
resolver: &net.Resolver{
|
resolver: &net.Resolver{
|
||||||
PreferGo: true,
|
PreferGo: true,
|
||||||
},
|
},
|
||||||
|
|
@ -96,6 +98,11 @@ func (r *RBLChecker) CheckEmail(email *EmailMessage) *RBLResults {
|
||||||
results.ListedCount++
|
results.ListedCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only check the first IP unless CheckAllIPs is enabled
|
||||||
|
if !r.CheckAllIPs {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func TestNewRBLChecker(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
checker := NewRBLChecker(tt.timeout, tt.rbls)
|
checker := NewRBLChecker(tt.timeout, tt.rbls, false)
|
||||||
if checker.Timeout != tt.expectedTimeout {
|
if checker.Timeout != tt.expectedTimeout {
|
||||||
t.Errorf("Timeout = %v, want %v", checker.Timeout, tt.expectedTimeout)
|
t.Errorf("Timeout = %v, want %v", checker.Timeout, tt.expectedTimeout)
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +97,7 @@ func TestReverseIP(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
checker := NewRBLChecker(5*time.Second, nil)
|
checker := NewRBLChecker(5*time.Second, nil, false)
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
@ -157,7 +157,7 @@ func TestIsPublicIP(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
checker := NewRBLChecker(5*time.Second, nil)
|
checker := NewRBLChecker(5*time.Second, nil, false)
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
@ -237,7 +237,7 @@ func TestExtractIPs(t *testing.T) {
|
||||||
},*/
|
},*/
|
||||||
}
|
}
|
||||||
|
|
||||||
checker := NewRBLChecker(5*time.Second, nil)
|
checker := NewRBLChecker(5*time.Second, nil, false)
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
@ -322,7 +322,7 @@ func TestGetBlacklistScore(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
checker := NewRBLChecker(5*time.Second, nil)
|
checker := NewRBLChecker(5*time.Second, nil, false)
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
@ -351,7 +351,7 @@ func TestGetUniqueListedIPs(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
checker := NewRBLChecker(5*time.Second, nil)
|
checker := NewRBLChecker(5*time.Second, nil, false)
|
||||||
listedIPs := checker.GetUniqueListedIPs(results)
|
listedIPs := checker.GetUniqueListedIPs(results)
|
||||||
|
|
||||||
expectedIPs := []string{"198.51.100.1", "198.51.100.2"}
|
expectedIPs := []string{"198.51.100.1", "198.51.100.2"}
|
||||||
|
|
@ -376,7 +376,7 @@ func TestGetRBLsForIP(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
checker := NewRBLChecker(5*time.Second, nil)
|
checker := NewRBLChecker(5*time.Second, nil, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,13 @@ func NewReportGenerator(
|
||||||
dnsTimeout time.Duration,
|
dnsTimeout time.Duration,
|
||||||
httpTimeout time.Duration,
|
httpTimeout time.Duration,
|
||||||
rbls []string,
|
rbls []string,
|
||||||
|
checkAllIPs bool,
|
||||||
) *ReportGenerator {
|
) *ReportGenerator {
|
||||||
return &ReportGenerator{
|
return &ReportGenerator{
|
||||||
authAnalyzer: NewAuthenticationAnalyzer(),
|
authAnalyzer: NewAuthenticationAnalyzer(),
|
||||||
spamAnalyzer: NewSpamAssassinAnalyzer(),
|
spamAnalyzer: NewSpamAssassinAnalyzer(),
|
||||||
dnsAnalyzer: NewDNSAnalyzer(dnsTimeout),
|
dnsAnalyzer: NewDNSAnalyzer(dnsTimeout),
|
||||||
rblChecker: NewRBLChecker(dnsTimeout, rbls),
|
rblChecker: NewRBLChecker(dnsTimeout, rbls, checkAllIPs),
|
||||||
contentAnalyzer: NewContentAnalyzer(httpTimeout),
|
contentAnalyzer: NewContentAnalyzer(httpTimeout),
|
||||||
headerAnalyzer: NewHeaderAnalyzer(),
|
headerAnalyzer: NewHeaderAnalyzer(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewReportGenerator(t *testing.T) {
|
func TestNewReportGenerator(t *testing.T) {
|
||||||
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs)
|
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false)
|
||||||
if gen == nil {
|
if gen == nil {
|
||||||
t.Fatal("Expected report generator, got nil")
|
t.Fatal("Expected report generator, got nil")
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +55,7 @@ func TestNewReportGenerator(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnalyzeEmail(t *testing.T) {
|
func TestAnalyzeEmail(t *testing.T) {
|
||||||
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs)
|
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false)
|
||||||
|
|
||||||
email := createTestEmail()
|
email := createTestEmail()
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ func TestAnalyzeEmail(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateReport(t *testing.T) {
|
func TestGenerateReport(t *testing.T) {
|
||||||
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs)
|
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false)
|
||||||
testID := uuid.New()
|
testID := uuid.New()
|
||||||
|
|
||||||
email := createTestEmail()
|
email := createTestEmail()
|
||||||
|
|
@ -130,7 +130,7 @@ func TestGenerateReport(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateReportWithSpamAssassin(t *testing.T) {
|
func TestGenerateReportWithSpamAssassin(t *testing.T) {
|
||||||
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs)
|
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false)
|
||||||
testID := uuid.New()
|
testID := uuid.New()
|
||||||
|
|
||||||
email := createTestEmailWithSpamAssassin()
|
email := createTestEmailWithSpamAssassin()
|
||||||
|
|
@ -150,7 +150,7 @@ func TestGenerateReportWithSpamAssassin(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateRawEmail(t *testing.T) {
|
func TestGenerateRawEmail(t *testing.T) {
|
||||||
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs)
|
gen := NewReportGenerator(10*time.Second, 10*time.Second, DefaultRBLs, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue