diff --git a/api/openapi.yaml b/api/openapi.yaml index 8c8a836..92bf3e3 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -202,6 +202,39 @@ paths: schema: $ref: '#/components/schemas/Error' + /blacklist: + post: + tags: + - tests + summary: Check an IP address against DNS blacklists + description: Tests a single IP address (IPv4 or IPv6) against configured DNS-based blacklists (RBLs) without requiring an actual email to be sent. Returns results immediately. + operationId: checkBlacklist + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BlacklistCheckRequest' + responses: + '200': + description: Blacklist check completed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/BlacklistCheckResponse' + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /status: get: tags: @@ -1182,3 +1215,48 @@ components: example: "A" dns_results: $ref: '#/components/schemas/DNSResults' + + BlacklistCheckRequest: + type: object + required: + - ip + properties: + ip: + type: string + description: IPv4 or IPv6 address to check against blacklists + example: "192.0.2.1" + pattern: '^([0-9]{1,3}\.){3}[0-9]{1,3}$|^([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}$|^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$|^([0-9a-fA-F]{0,4}:){1,6}:([0-9a-fA-F]{0,4}:){0,5}[0-9a-fA-F]{0,4}$' + + BlacklistCheckResponse: + type: object + required: + - ip + - checks + - listed_count + - score + - grade + properties: + ip: + type: string + description: The IP address that was checked + example: "192.0.2.1" + checks: + type: array + items: + $ref: '#/components/schemas/BlacklistCheck' + description: List of blacklist check results + listed_count: + type: integer + description: Number of blacklists that have this IP listed + example: 0 + score: + type: integer + minimum: 0 + maximum: 100 + description: Blacklist score (0-100, higher is better) + example: 100 + grade: + type: string + enum: [A+, A, B, C, D, E, F] + description: Letter grade representation of the score + example: "A+" diff --git a/internal/api/handlers.go b/internal/api/handlers.go index fd57579..80c8f9a 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -41,6 +41,7 @@ import ( type EmailAnalyzer interface { AnalyzeEmailBytes(rawEmail []byte, testID uuid.UUID) (reportJSON []byte, err error) AnalyzeDomain(domain string) (dnsResults *DNSResults, score int, grade string) + CheckBlacklistIP(ip string) (checks []BlacklistCheck, listedCount int, score int, grade string, err error) } // APIHandler implements the ServerInterface for handling API requests @@ -341,3 +342,41 @@ func (h *APIHandler) TestDomain(c *gin.Context) { c.JSON(http.StatusOK, response) } + +// CheckBlacklist checks an IP address against DNS blacklists +// (POST /blacklist) +func (h *APIHandler) CheckBlacklist(c *gin.Context) { + var request BlacklistCheckRequest + + // Bind and validate request + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, Error{ + Error: "invalid_request", + Message: "Invalid request body", + Details: stringPtr(err.Error()), + }) + return + } + + // Perform blacklist check using analyzer + checks, listedCount, score, grade, err := h.analyzer.CheckBlacklistIP(request.Ip) + if err != nil { + c.JSON(http.StatusBadRequest, Error{ + Error: "invalid_ip", + Message: "Invalid IP address", + Details: stringPtr(err.Error()), + }) + return + } + + // Build response + response := BlacklistCheckResponse{ + Ip: request.Ip, + Checks: checks, + ListedCount: listedCount, + Score: score, + Grade: BlacklistCheckResponseGrade(grade), + } + + c.JSON(http.StatusOK, response) +} diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 1cc5bf1..e7ae561 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -119,3 +119,23 @@ func (a *APIAdapter) AnalyzeDomain(domain string) (*api.DNSResults, int, string) return dnsResults, score, grade } + +// CheckBlacklistIP checks a single IP address against DNS blacklists +func (a *APIAdapter) CheckBlacklistIP(ip string) ([]api.BlacklistCheck, int, int, string, error) { + // Check the IP against all configured RBLs + checks, listedCount, err := a.analyzer.generator.rblChecker.CheckIP(ip) + if err != nil { + return nil, 0, 0, "", err + } + + // Calculate score using the existing function + // Create a minimal RBLResults structure for scoring + results := &RBLResults{ + Checks: map[string][]api.BlacklistCheck{ip: checks}, + IPsChecked: []string{ip}, + ListedCount: listedCount, + } + score, grade := a.analyzer.generator.rblChecker.CalculateRBLScore(results) + + return checks, listedCount, score, grade, nil +} diff --git a/pkg/analyzer/rbl.go b/pkg/analyzer/rbl.go index 5e8b503..5fcb939 100644 --- a/pkg/analyzer/rbl.go +++ b/pkg/analyzer/rbl.go @@ -108,6 +108,28 @@ func (r *RBLChecker) CheckEmail(email *EmailMessage) *RBLResults { return results } +// CheckIP checks a single IP address against all configured RBLs +func (r *RBLChecker) CheckIP(ip string) ([]api.BlacklistCheck, int, error) { + // Validate that it's a valid IP address + if !r.isPublicIP(ip) { + return nil, 0, fmt.Errorf("invalid or non-public IP address: %s", ip) + } + + var checks []api.BlacklistCheck + listedCount := 0 + + // Check the IP against all RBLs + for _, rbl := range r.RBLs { + check := r.checkIP(ip, rbl) + checks = append(checks, check) + if check.Listed { + listedCount++ + } + } + + return checks, listedCount, nil +} + // extractIPs extracts IP addresses from Received headers func (r *RBLChecker) extractIPs(email *EmailMessage) []string { var ips []string diff --git a/web/routes.go b/web/routes.go index c60cb11..44b1cb2 100644 --- a/web/routes.go +++ b/web/routes.go @@ -63,6 +63,10 @@ func DeclareRoutes(cfg *config.Config, router *gin.Engine) { appConfig["survey_url"] = cfg.SurveyURL.String() } + if len(cfg.Analysis.RBLs) > 0 { + appConfig["rbls"] = cfg.Analysis.RBLs + } + if appcfg, err := json.MarshalIndent(appConfig, "", " "); err != nil { log.Println("Unable to generate JSON config to inject in web application") } else { diff --git a/web/src/lib/stores/config.ts b/web/src/lib/stores/config.ts index 4187307..8a978e0 100644 --- a/web/src/lib/stores/config.ts +++ b/web/src/lib/stores/config.ts @@ -29,6 +29,7 @@ interface AppConfig { const defaultConfig: AppConfig = { report_retention: 0, survey_url: "", + rbls: [], }; function getConfigFromScriptTag(): AppConfig | null { diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 2cf556b..d3f17a3 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -235,9 +235,13 @@
diff --git a/web/src/routes/blacklist/+page.svelte b/web/src/routes/blacklist/+page.svelte new file mode 100644 index 0000000..f104e73 --- /dev/null +++ b/web/src/routes/blacklist/+page.svelte @@ -0,0 +1,186 @@ + + ++ Test an IP address against multiple DNS-based blacklists (RBLs) to check its reputation. +
++ DNS-based blacklists (RBLs) are used by email servers to identify and block spam sources. Being listed can severely impact email deliverability. +
++ This tool checks your IP against multiple popular RBLs to help you: +
++ For comprehensive deliverability testing including DKIM verification, content analysis, spam scoring, and more: +
+ + + Send Test Email + +Querying DNS-based blacklists
+{error}
+ ++ This IP address is not listed on any checked blacklists. +
++ This IP address is listed on {result.listed_count} of {result.checks.length} checked blacklist{result.checks.length > 1 ? "s" : ""}. +
++ Good news! This IP address is not currently listed on any of the + checked DNS-based blacklists (RBLs). This indicates a good sender reputation + and should not negatively impact email deliverability. +
+ {:else} ++ Warning: This IP address is listed on {result.listed_count} blacklist{result.listed_count > 1 ? "s" : ""}. + Being listed can significantly impact email deliverability as many mail servers + use these blacklists to filter incoming mail. +
++ This blacklist check tests IP reputation only. For comprehensive + deliverability testing including DKIM verification, content analysis, + spam scoring, and DNS configuration: +
+ + + Send Test Email + +