diff --git a/app/lib/ip_blacklist/smtp_response_parser.rb b/app/lib/ip_blacklist/smtp_response_parser.rb index 96f97ae2e..d2260a338 100644 --- a/app/lib/ip_blacklist/smtp_response_parser.rb +++ b/app/lib/ip_blacklist/smtp_response_parser.rb @@ -159,6 +159,28 @@ class SMTPResponseParser }, ].freeze + # Proofpoint-specific patterns (optimized for ReDoS protection) + PROOFPOINT_PATTERNS = [ + { + regex: /\A.{0,200}554[- ]5\.7\.0.{0,50}Blocked.{0,200}proofpoint\.com\/dnsbl-lookup/i, + source: "proofpoint_dnsbl_block", + severity: "high", + description: "Proofpoint DNSBL blocking - IP listed on Proofpoint's reputation database" + }, + { + regex: /\A.{0,200}554[- ]5\.7\.1.{0,50}Service unavailable.{0,100}proofpoint/i, + source: "proofpoint_reputation_block", + severity: "high", + description: "Proofpoint reputation-based blocking" + }, + { + regex: /\A.{0,200}421[- ]4\.7\.1.{0,100}proofpoint/i, + source: "proofpoint_temporary_block", + severity: "medium", + description: "Proofpoint temporary deferral - possible reputation issue" + }, + ].freeze + # Parse SMTP response message and code # # @param message [String] The SMTP error message @@ -195,7 +217,8 @@ def self.parse(message, smtp_code) # Check for provider-specific patterns first (most specific) # iCloud patterns checked before Gmail to avoid conflicts with generic patterns # then generic DNSBL patterns (fallback) - check_icloud_patterns(safe_message, result) || + check_proofpoint_patterns(safe_message, result) || + check_icloud_patterns(safe_message, result) || check_gmail_patterns(safe_message, result) || check_outlook_patterns(safe_message, result) || check_yahoo_patterns(safe_message, result) || @@ -306,6 +329,20 @@ def self.check_icloud_patterns(message, result) false end + # @private + def self.check_proofpoint_patterns(message, result) + PROOFPOINT_PATTERNS.each do |pattern| + next unless message =~ pattern[:regex] + + result[:blacklist_detected] = true + result[:blacklist_source] = pattern[:source] + result[:severity] = pattern[:severity] + result[:description] = pattern[:description] + return true + end + false + end + # @private def self.check_generic_dnsbl_patterns(message, result) DNSBL_PATTERNS.each do |pattern, source| diff --git a/app/views/ip_blacklist_records/show.html.haml b/app/views/ip_blacklist_records/show.html.haml index 03e91851f..34e882c95 100644 --- a/app/views/ip_blacklist_records/show.html.haml +++ b/app/views/ip_blacklist_records/show.html.haml @@ -68,6 +68,22 @@ %th Updated At %td= @record.updated_at.strftime("%Y-%m-%d %H:%M:%S") + - if @record.detected_via_smtp? && @record.smtp_response_message.present? + %h2.sectionTitle SMTP Response Details + + %table.dataTable.u-margin + %tbody + - if @record.smtp_response_code.present? + %tr + %th{:width => "30%"} SMTP Response Code + %td + %code.label.label--red= @record.smtp_response_code + %tr + %th SMTP Response Message + %td + .codeBlock + %pre= @record.smtp_response_message + - if @record.detected_via_smtp? && @record.active? %h2.sectionTitle Retry Information diff --git a/spec/lib/ip_blacklist/smtp_response_parser_spec.rb b/spec/lib/ip_blacklist/smtp_response_parser_spec.rb index 0f0e6451f..e429916ea 100644 --- a/spec/lib/ip_blacklist/smtp_response_parser_spec.rb +++ b/spec/lib/ip_blacklist/smtp_response_parser_spec.rb @@ -217,6 +217,39 @@ end end + context "Proofpoint patterns" do + it "detects Proofpoint DNSBL block" do + message = "554 5.7.0 Blocked - see https://support.proofpoint.com/dnsbl-lookup.cgi?ip=5.196.61.193" + result = described_class.parse(message, "554") + + expect(result[:blacklist_detected]).to be true + expect(result[:blacklist_source]).to eq("proofpoint_dnsbl_block") + expect(result[:severity]).to eq("high") + expect(result[:bounce_type]).to eq("hard") + expect(result[:suggested_action]).to eq("pause_immediately") + end + + it "detects Proofpoint reputation block" do + message = "554 5.7.1 Service unavailable; Host is blocked by Proofpoint" + result = described_class.parse(message, "554") + + expect(result[:blacklist_detected]).to be true + expect(result[:blacklist_source]).to eq("proofpoint_reputation_block") + expect(result[:severity]).to eq("high") + end + + it "detects Proofpoint temporary block" do + message = "421 4.7.1 Service temporarily unavailable - contact Proofpoint for assistance" + result = described_class.parse(message, "421") + + expect(result[:blacklist_detected]).to be true + expect(result[:blacklist_source]).to eq("proofpoint_temporary_block") + expect(result[:severity]).to eq("medium") + expect(result[:bounce_type]).to eq("soft") + expect(result[:suggested_action]).to eq("monitor_closely") + end + end + context "Generic DNSBL patterns" do it "detects Spamhaus ZEN listing" do message = "554 Service unavailable; Client host [192.0.2.1] blocked using zen.spamhaus.org"