Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion app/lib/ip_blacklist/smtp_response_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) ||
Expand Down Expand Up @@ -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|
Expand Down
16 changes: 16 additions & 0 deletions app/views/ip_blacklist_records/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
33 changes: 33 additions & 0 deletions spec/lib/ip_blacklist/smtp_response_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down