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

Implement #290 (Sisimai::Rhost Improvement) #291

Merged
merged 15 commits into from
Jun 3, 2024
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
1 change: 1 addition & 0 deletions lib/sisimai/fact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ def self.rise(**argvs)
o['reason'] = '' if o['reason'].start_with?('onhold', 'undefined')
re = ''; de = o['destination']
re = Sisimai::Rhost.get(o) if Sisimai::Rhost.match(o['rhost'])

if re.empty?
# Failed to detect a bounce reason by the value of "rhost"
re = Sisimai::Rhost.get(o, de) if Sisimai::Rhost.match(de)
Expand Down
3 changes: 2 additions & 1 deletion lib/sisimai/rhost.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def get(argvs, proxy = nil)
remotehost = proxy || argvs['rhost'].downcase
rhostclass = ''
modulename = ''
return '' if argvs['diagnosticcode'].empty?

RhostClass.each_key do |e|
# Try to match with each key of RhostClass
Expand All @@ -54,7 +55,7 @@ def get(argvs, proxy = nil)
rhostclass = 'sisimai/rhost/' << e.downcase
break
end
return nil if rhostclass.empty?
return '' if rhostclass.empty?

require rhostclass
reasontext = Module.const_get(modulename).get(argvs)
Expand Down
3 changes: 1 addition & 2 deletions lib/sisimai/rhost/apple.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ class << self
# @see https://support.apple.com/en-us/102322
# https://www.postmastery.com/icloud-postmastery-page/
# https://smtpfieldmanual.com/provider/apple
# @since v5.0.4
def get(argvs)
return argvs['reason'] unless argvs['reason'].empty?

issuedcode = argvs['diagnosticcode'].downcase
reasontext = ''

Expand Down
111 changes: 80 additions & 31 deletions lib/sisimai/rhost/cox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,54 @@
module Cox
class << self
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.
# CXBL
# - The sending IP address has been blocked by Cox due to exhibiting spam-like behavior.
# - Send an email request to Cox to ask for a sending IP address be unblocked.
# Note: Cox has sole discretion whether to unblock the sending IP address.
'CXBL' => 'blocked',

# CXDNS
# - There was an issue with the connecting IP address Domain Name System (DNS).
# - The Reverse DNS (rDNS) lookup for your IP address is failing.
# - Confirm the IP address that sends your email.
# - Check the rDNS of that IP address. If it passes, then wait 24 hours and try resending
# your email.
'CXDNS' => 'requireptr',

# CXSNDR
# - There was a problem with the sender's domain.
# - Your email failed authentication checks against your sending domain's SPF, DomainKeys,
# or DKIM policy.
'CXSNDR' => 'authfailure',

# CXSMTP
# - There was a violation of SMTP protocol.
# - Your email wasn't delivered because Cox was unable to verify that it came from a
# legitimate email sender.
'CXSMTP' => 'rejected',

# CXCNCT
# - There was a connection issue from the IP address.
# - There is a limit to the number of concurrent SMTP connections per IP address to
# protect the systems against attack. Ensure that the sending email server is not
# opening more than 10 concurrent connections to avoid reaching this limit.
'CXCNCT' => 'toomanyconn',

# CXMXRT
# - The sender has sent email to too many recipients and needs to wait before sending
# more email.
# - The email sender has exceeded the maximum number of sent email allowed.
'CXMXRT' => 'toomanyconn',

# CDRBL
# - The sending IP address has been temporarily blocked by Cox due to exhibiting spam-like
# behavior.
# - The block duration varies depending on reputation and other factors, but will not exceed
# 24 hours. Inspect email traffic for potential spam, and retry email delivery.
'CDRBL' => 'blocked',

'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.
Expand All @@ -32,70 +70,81 @@
'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.
# - An email client has repeatedly sent bad commands or invalid passwords resulting in
# a three-hour block of the client's IP address.
# - The sending IP address has exceeded the threshold of invalid recipients and has
# been blocked.
'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',
'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',
'requireptr' => [
# - The reverse DNS check of the sending server IP address has failed.
# - Cox requires that all connecting email servers contain valid reverse DNS PTR records.
'dns check failure - try again later',
'rejected - no rdns',
],
'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.
# - The sending server has attempted to communicate too soon within the SMTP transaction
# - 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.
'esmtp no data before greeting',
'attachment extension is forbidden',
],
'rejected' => [
# Cox requires that all sender domains resolve to a valid MX or A-record within DNS.
'sender rejected',
],
'systemerror' => [
# - Our systems are experiencing an issue which is causing a temporary inability to
# accept new email.
'esmtp server temporarily not available',
],
'toomanyconn' => [
# The sending IP address has exceeded the five maximum concurrent connection limit.
# - The sending IP address has exceeded the five maximum concurrent connection limit.
# - The SMTP connection has exceeded the 100 email message threshold and was disconnected.
# - The sending IP address has exceeded one of these rate limits and has been temporarily
# blocked.
'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',
'message threshold exceeded',
],
'userunknown' => [
'recipient rejected', # The intended recipient is not a valid Cox Email account.
# - The intended recipient is not a valid Cox Email account.
'recipient rejected',
],
}.freeze

# Detect bounce reason from https://cox.com/
# @param [Sisimai::Fact] argvs Parsed email object
# @return [String, Nil] The bounce reason at Cox
# @see https://www.cox.com/residential/support/email-error-codes.html
# @since v4.25.8
def get(argvs)
statusmesg = argvs['diagnosticcode']
issuedcode = argvs['diagnosticcode']
codenumber = 0

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

issuedcode = argvs['diagnosticcode'].downcase
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) }
next unless MessagesOf[e].any? { |a| issuedcode.include?(a) }

Check warning on line 147 in lib/sisimai/rhost/cox.rb

View check run for this annotation

Codecov / codecov/patch

lib/sisimai/rhost/cox.rb#L147

Added line #L147 was not covered by tests
reasontext = e
break
end
Expand Down
Loading
Loading