Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sisimai::Rhost::Cox #193

Merged
merged 1 commit into from
Jul 12, 2020
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
2 changes: 1 addition & 1 deletion lib/sisimai/rfc3464.rb
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ def make(mhead, mbody)
elsif readslices[-2].start_with?('Diagnostic-Code:') && cv = e.match(/\A[ \t]+(.+)\z/)
# Continued line of the value of Diagnostic-Code header
v['diagnosis'] << ' ' << cv[1]
e = 'Diagnostic-Code: ' << e
readslices[-1] = 'Diagnostic-Code: ' << e
else
if cv = e.match(/\AReporting-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
# 2.2.2 The Reporting-MTA DSN field
Expand Down
1 change: 1 addition & 0 deletions lib/sisimai/rhost.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class << self
'lsean.ezweb.ne.jp' => 'KDDI',
'msmx.au.com' => 'KDDI',
'charter.net' => 'Spectrum',
'cox.net' => 'Cox',
'.qq.com' => 'TencentQQ',
}.freeze

Expand Down
112 changes: 112 additions & 0 deletions lib/sisimai/rhost/cox.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
module Sisimai
module Rhost
# Sisimai::Rhost detects the bounce reason from the content of Sisimai::Data
# object as an argument of get() method when the value of "destination" of
# the object is "charter.net". This class is called only Sisimai::Data class.
module Cox
class << self
# Imported from p5-Sisimail/lib/Sisimai/Rhost/Cox.pm
ErrorCodes = {
# https://www.cox.com/residential/support/email-error-codes.html
'CXBL' => 'blocked', # The sending IP address has been blocked by Cox due to exhibiting spam-like behavior.
'CXTHRT' => 'securityerror', # Email sending limited due to suspicious account activity.
'CXMJ' => 'securityerror', # Email sending blocked due to suspicious account activity on primary Cox account.
'CXDNS' => 'blocked', # There was an issue with the connecting IP address Domain Name System (DNS).
'CXSNDR' => 'rejected', # There was a problem with the sender's domain.
'CXSMTP' => 'rejected', # Your email wasn't delivered because Cox was unable to verify that it came from a legitimate email sender.
'CXCNCT' => 'toomanyconn', # There is a limit to the number of concurrent SMTP connections per IP address
'CXMXRT' => 'toomanyconn', # The email sender has exceeded the maximum number of sent email allowed.
'CDRBL' => 'blocked', # The sending IP address has been temporarily blocked by Cox due to exhibiting spam-like behavior.
'IPBL0001' => 'blocked', # The sending IP address is listed in the Spamhaus Zen DNSBL.
'IPBL0010' => 'blocked', # The sending IP is listed in the Return Path DNSBL.
'IPBL0100' => 'blocked', # The sending IP is listed in the Invaluement ivmSIP DNSBL.
'IPBL0011' => 'blocked', # The sending IP is in the Spamhaus Zen and Return Path DNSBLs.
'IPBL0101' => 'blocked', # The sending IP is in the Spamhaus Zen and Invaluement ivmSIP DNSBLs.
'IPBL0110' => 'blocked', # The sending IP is in the Return Path and Invaluement ivmSIP DNSBLs.
'IPBL0111' => 'blocked', # The sending IP is in the Spamhaus Zen, Return Path and Invaluement ivmSIP DNSBLs.
'IPBL1000' => 'blocked', # The sending IP address is listed on a CSI blacklist. You can check your status on the CSI website.
'IPBL1001' => 'blocked', # The sending IP is listed in the Cloudmark CSI and Spamhaus Zen DNSBLs.
'IPBL1010' => 'blocked', # The sending IP is listed in the Cloudmark CSI and Return Path DNSBLs.
'IPBL1011' => 'blocked', # The sending IP is in the Cloudmark CSI, Spamhaus Zen and Return Path DNSBLs.
'IPBL1100' => 'blocked', # The sending IP is listed in the Cloudmark CSI and Invaluement ivmSIP DNSBLs.
'IPBL1101' => 'blocked', # The sending IP is in the Cloudmark CSI, Spamhaus Zen and Invaluement IVMsip DNSBLs.
'IPBL1110' => 'blocked', # The sending IP is in the Cloudmark CSI, Return Path and Invaluement ivmSIP DNSBLs.
'IPBL1111' => 'blocked', # The sending IP is in the Cloudmark CSI, Spamhaus Zen, Return Path and Invaluement ivmSIP DNSBLs.
'IPBL00001' => 'blocked', # The sending IP address is listed on a Spamhaus blacklist. Check your status at Spamhaus.
'URLBL011' => 'spamdetected', # A URL within the body of the message was found on blocklists SURBL and Spamhaus DBL.
'URLBL101' => 'spamdetected', # A URL within the body of the message was found on blocklists SURBL and ivmURI.
'URLBL110' => 'spamdetected', # A URL within the body of the message was found on blocklists Spamhaus DBL and ivmURI.
'URLBL1001' => 'spamdetected', # The URL is listed on a Spamhaus blacklist. Check your status at Spamhaus.
}.freeze
MessagesOf = {
'blocked' => [
# Cox requires that all connecting email servers contain valid reverse DNS PTR records.
'rejected - no rDNS',
# An email client has repeatedly sent bad commands or invalid passwords resulting in a three-hour block of the client's IP address.
'cox too many bad commands from',
# The reverse DNS check of the sending server IP address has failed.
'DNS check failure - try again later',
# The sending IP address has exceeded the threshold of invalid recipients and has been blocked.
'Too many invalid recipients',
],
'notaccept' => [
# Our systems are experiencing an issue which is causing a temporary inability to accept new email.
'ESMTP server temporarily not available',
],
'policyviolation' => [
# The sending server has attempted to communicate too soon within the SMTP transaction
'ESMTP no data before greeting',
# The message has been rejected because it contains an attachment with one of the following prohibited
# file types, which commonly contain viruses: .shb, .shs, .vbe, .vbs, .wsc, .wsf, .wsh, .pif, .msc,
# .msi, .msp, .reg, .sct, .bat, .chm, .isp, .cpl, .js, .jse, .scr, .exe.
'attachment extension is forbidden',
],
'rejected' => [
# Cox requires that all sender domains resolve to a valid MX or A-record within DNS.
'sender rejected',
],
'toomanyconn' => [
# The sending IP address has exceeded the five maximum concurrent connection limit.
'too many sessions from',
# The SMTP connection has exceeded the 100 email message threshold and was disconnected.
'requested action aborted: try again later',
# The sending IP address has exceeded one of these rate limits and has been temporarily blocked.
'Message threshold exceeded',
],
'userunknown' => [
'recipient rejected', # The intended recipient is not a valid Cox Email account.
],
}.freeze

# Detect bounce reason from https://cox.com/
# @param [Sisimai::Data] argvs Parsed email object
# @return [String, Nil] The bounce reason at Cox
# @since v4.25.8
def get(argvs)
statusmesg = argvs.diagnosticcode
codenumber = 0

if cv = statusmesg.match(/AUP#([0-9A-Z]+)/)
# Capture the numeric part of the error code
codenumber = cv[1]
end
reasontext = ErrorCodes[codenumber] || ''

if reasontext.empty?
# The error code was not found in ErrorCodes
MessagesOf.each_key do |e|
# Try to find with each error message defined in MessagesOf
next unless MessagesOf[e].any? { |a| statusmesg.include?(a) }
reasontext = e
break
end
end

return reasontext
end

end
end
end
end

192 changes: 192 additions & 0 deletions set-of-emails/maildir/bsd/rhost-cox-01.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
From MAILER-DAEMON Thu Jul 2 12:05:05 2020
Received: from mailer76.example.com (mailer76.example.com [111.22.33.44])
by bouncehandler.example.com (Postfix) with ESMTPS id 7939F4114A
for <bounce@mailer.cnt1.example.com>; Thu, 2 Jul 2020 12:05:05 -0400 (EDT)
Received: by tr2.example.com (Postfix)
id 6F3F839637A1; Thu, 2 Jul 2020 12:05:05 -0400 (EDT)
Date: Thu, 2 Jul 2020 12:05:05 -0400 (EDT)
From: MAILER-DAEMON@tr2.example.com (Mail Delivery System)
Subject: Undelivered Mail Returned to Sender
To: bounce@mailer.cnt1.example.com
Auto-Submitted: auto-replied
MIME-Version: 1.0
Content-Type: multipart/report; report-type=delivery-status;
boundary="D9E933966464.1593705905/tr2.example.com"
Content-Transfer-Encoding: 8bit
Message-Id: <20200702160505.6F3F839637A1@tr2.example.com>

This is a MIME-encapsulated message.

--D9E933966464.1593705905/tr2.example.com
Content-Description: Notification
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit

This is the mail system at host tr2.example.com.

I'm sorry to have to inform you that your message could not
be delivered to one or more recipients. It's attached below.

For further assistance, please send mail to postmaster.

If you do so, please include this problem report. You can
delete your own text from the attached returned message.

The mail system

<recipient55@cox.net>: host cxr.mx.a.cloudfilter.net[34.212.80.54] said: 550
5.1.0 <bounce@mailer.cnt1.example.com> sender rejected. Refer to Error
Codes section at
https://www.cox.com/residential/support/email-error-codes.html for more
information. AUP#CDRBL (in reply to MAIL FROM command)

--D9E933966464.1593705905/tr2.example.com
Content-Description: Delivery report
Content-Type: message/delivery-status

Reporting-MTA: dns; tr2.example.com
X-Postfix-Queue-ID: D9E933966464
X-Postfix-Sender: rfc822; bounce@mailer.cnt1.example.com
Arrival-Date: Thu, 2 Jul 2020 06:04:42 -0400 (EDT)

Final-Recipient: rfc822; recipient55@cox.net
Original-Recipient: rfc822;recipient55@cox.net
Action: failed
Status: 5.1.0
Remote-MTA: dns; cxr.mx.a.cloudfilter.net
Diagnostic-Code: smtp; 550 5.1.0 <bounce@mailer.cnt1.example.com> sender
rejected. Refer to Error Codes section at
https://www.cox.com/residential/support/email-error-codes.html for more
information. AUP#CDRBL

--D9E933966464.1593705905/tr2.example.com
Content-Description: Undelivered Message Headers
Content-Type: text/rfc822-headers
Content-Transfer-Encoding: 8bit

Return-Path: <bounce@mailer.cnt1.example.com>
Received: from pixel1.example.com (pixel1 [104.207.134.97])
by tr2.example.com (Postfix) with ESMTPA id D9E933966464
for <recipient55@cox.net>; Thu, 2 Jul 2020 06:04:42 -0400 (EDT)
DKIM-Filter: OpenDKIM Filter v2.11.0 tr2.example.com D9E933966464
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com;
s=mail; t=1593684282;
bh=QCJqru6dH/ii/ts4AH9NsNa/v562Fc43deLk8w1KKJ8=;
h=To:Subject:Date:From:Reply-To:List-Unsubscribe:From;
b=EXtMRJLuGhK9gq/gZspsdSSTRmLXOTxQstFekcdNQsxLxKEHmOijeZWiDlz8W8FKn
95nVfTtlOk8dCFE/4EeLyBWlPoQ0EdpLPUcEcwHbBynoCQHwRI1iUiE4r2+Mc5iX51
1eRfb2xphpVwWyJhA2I7qXAeOuJ27J6cdRI7ijlI=
Received: by pixel1.example.com (Postfix, from userid 501)
id C3726626BC; Thu, 2 Jul 2020 06:04:42 -0400 (EDT)
To: "recipient55@cox.net" <recipient55@cox.net>
Subject: Take down your patio umbrella
X-PHP-Originating-Script: 501:phpmailer.php
Date: Thu, 2 Jul 2020 06:04:42 -0400
From: deas <emma@cnt1.example.com>
Reply-To: Example <no-reply@example.com>
Message-ID: <5119e0e9531a692224d1bd44ef560e62@pixel1.example.com>
X-Priority: 3
X-Mailer: PHPMailer 5.2.7 (https://github.com/PHPMailer/PHPMailer/)
X-se: fol_new_t
X-date: 20200702
X-version: version_C_ht
X-SES-CONFIGURATION-SET: ded-content
X-CONFIGSET: conf-content
Feedback-ID: 21053362:version_C_ht:newsletter:example
List-Unsubscribe: https://www.example.com/unsubscribe
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="b1_5119e0e9531a692224d1bd44ef560e62"
Content-Transfer-Encoding: 8bit

--D9E933966464.1593705905/tr2.example.com--
From double-bounce@tr2.example.com Thu Jul 2 12:05:05 2020
Received: from mailer74.example.com (mailer74.example.com [108.61.68.74])
by bouncehandler.example.com (Postfix) with ESMTPS id 803294114A
for <bounce@mailer.example.com>; Thu, 2 Jul 2020 12:05:05 -0400 (EDT)
Received: by tr2.example.com (Postfix)
id 75BA239637A5; Thu, 2 Jul 2020 12:05:05 -0400 (EDT)
Date: Thu, 2 Jul 2020 12:05:05 -0400 (EDT)
From: MAILER-DAEMON@tr2.example.com (Mail Delivery System)
Subject: Postmaster Copy: Undelivered Mail
To: bounce@mailer.example.com
Auto-Submitted: auto-generated
MIME-Version: 1.0
Content-Type: multipart/report; report-type=delivery-status;
boundary="D9E933966464.1593705905/tr2.example.com"
Content-Transfer-Encoding: 8bit
Message-Id: <20200702160505.75BA239637A5@tr2.example.com>

This is a MIME-encapsulated message.

--D9E933966464.1593705905/tr2.example.com
Content-Description: Notification
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit


<recipient55@cox.net>: host cxr.mx.a.cloudfilter.net[34.212.80.54] said: 550
5.1.0 <bounce@mailer.cnt1.example.com> sender rejected. Refer to Error
Codes section at
https://www.cox.com/residential/support/email-error-codes.html for more
information. AUP#CDRBL (in reply to MAIL FROM command)

--D9E933966464.1593705905/tr2.example.com
Content-Description: Delivery report
Content-Type: message/delivery-status

Reporting-MTA: dns; tr2.example.com
X-Postfix-Queue-ID: D9E933966464
X-Postfix-Sender: rfc822; bounce@mailer.cnt1.example.com
Arrival-Date: Thu, 2 Jul 2020 06:04:42 -0400 (EDT)

Final-Recipient: rfc822; recipient55@cox.net
Original-Recipient: rfc822;recipient55@cox.net
Action: failed
Status: 5.1.0
Remote-MTA: dns; cxr.mx.a.cloudfilter.net
Diagnostic-Code: smtp; 550 5.1.0 <bounce@mailer.cnt1.example.com> sender
rejected. Refer to Error Codes section at
https://www.cox.com/residential/support/email-error-codes.html for more
information. AUP#CDRBL

--D9E933966464.1593705905/tr2.example.com
Content-Description: Undelivered Message Headers
Content-Type: text/rfc822-headers
Content-Transfer-Encoding: 8bit

Return-Path: <bounce@mailer.cnt1.example.com>
Received: from pixel1.example.com (pixel1 [111.22.33.44])
by tr2.example.com (Postfix) with ESMTPA id D9E933966464
for <recipient55@cox.net>; Thu, 2 Jul 2020 06:04:42 -0400 (EDT)
DKIM-Filter: OpenDKIM Filter v2.11.0 tr2.example.com D9E933966464
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com;
s=mail; t=1593684282;
bh=QCJqru6dH/ii/ts4AH9NsNa/v562Fc43deLk8w1KKJ8=;
h=To:Subject:Date:From:Reply-To:List-Unsubscribe:From;
b=EXtMRJLuGhK9gq/gZspsdSSTRmLXOTxQstFekcdNQsxLxKEHmOijeZWiDlz8W8FKn
95nVfTtlOk8dCFE/4EeLyBWlPoQ0EdpLPUcEcwHbBynoCQHwRI1iUiE4r2+Mc5iX51
1eRfb2xphpVwWyJhA2I7qXAeOuJ27J6cdRI7ijlI=
Received: by pixel1.example.com (Postfix, from userid 501)
id C3726626BC; Thu, 2 Jul 2020 06:04:42 -0400 (EDT)
To: "recipient55@cox.net" <recipient55@cox.net>
Subject: Take down your patio umbrella
X-PHP-Originating-Script: 501:phpmailer.php
Date: Thu, 2 Jul 2020 06:04:42 -0400
From: Ideas <emma.jones@cnt1.example.com>
Reply-To: Example <no-reply@example.com>
Message-ID: <5119e0e9531a692224d1bd44ef560e62@pixel1.example.com>
X-Priority: 3
X-Mailer: PHPMailer 5.2.7 (https://github.com/PHPMailer/PHPMailer/)
X-se: fol_new_t
X-date: 20200702
X-version: version_C_ht
X-SES-CONFIGURATION-SET: ded-content
X-CONFIGSET: conf-content
List-Unsubscribe: https://www.example.com/unsubscribe
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="b1_5119e0e9531a692224d1bd44ef560e62"
Content-Transfer-Encoding: 8bit

--D9E933966464.1593705905/tr2.example.com--
2 changes: 1 addition & 1 deletion spec/sisimai/mail/maildir_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

describe Sisimai::Mail::Maildir do
samplemaildir = './set-of-emails/maildir/bsd'
allofthefiles = 489
allofthefiles = 490
let(:mailobj) { Sisimai::Mail::Maildir.new(samples) }
let(:mockobj) { Sisimai::Mail::Maildir.new(invalid) }

Expand Down
52 changes: 52 additions & 0 deletions spec/sisimai/rhost/cox_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'spec_helper'
require 'sisimai/mail'
require 'sisimai/data'
require 'sisimai/message'
require 'sisimai/rhost/cox'

describe Sisimai::Rhost::Cox do
rs = {
'01' => { 'status' => %r/\A5[.]1[.]0\z/, 'reason' => %r/blocked/ },
}
describe 'bounce mail from Cox' do
rs.each_key.each do |n|
emailfn = sprintf('./set-of-emails/maildir/bsd/rhost-cox-%02d.eml', n)
next unless File.exist?(emailfn)

mailbox = Sisimai::Mail.new(emailfn)
mtahost = /cox[.]net\z/
next unless mailbox

while r = mailbox.data.read do
mesg = Sisimai::Message.new(data: r)
it('is Sisimai::Message object') { expect(mesg).to be_a Sisimai::Message }
it('has array in "ds" accessor' ) { expect(mesg.ds).to be_a Array }
it('has hash in "header" accessor' ) { expect(mesg.header).to be_a Hash }
it('has hash in "rfc822" accessor' ) { expect(mesg.rfc822).to be_a Hash }
it('has From line in "from" accessor' ) { expect(mesg.from.size).to be > 0 }

mesg.ds.each do |e|
example('spec is "SMTP"') { expect(e['spec']).to be == 'SMTP' }
example 'recipient is email address' do
expect(e['recipient']).to match(/\A.+[@].+[.].+\z/)
end
example('status is DSN') { expect(e['status']).to match(/\A\d[.]\d[.]\d\z/) }
example('command exists ') { expect(e['command']).to match(/\A(?:MAIL|)\z/) }
example('diagnosis is not empty') { expect(e['diagnosis']).not_to be_empty }
example('action is not empty') { expect(e['action']).not_to be_empty }
example('recipient ends with ' + mtahost.to_s) { expect(e['recipient']).to match(mtahost) }
example('alias exists') { expect(e['alias']).not_to be_nil }
example('agent is ' + e['agent']) { expect(e['agent']).to match(/\A(?:Postfix|RFC3464)/) }
end

data = Sisimai::Data.make(data: mesg)
data.each do |e|
example('reason is String') { expect(e.reason.size).to be > 0 }
example('reason matches') { expect(e.reason).to match(rs[n]['reason']) }
end
end
end

end
end