From 41e088f769c1458a634fb0d7f62c5d0cb6185be8 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sat, 18 Feb 2023 00:39:00 +0900 Subject: [PATCH 01/37] Implement Sisimai::SMTP::Command.test() and tests for the method --- lib/sisimai/smtp/command.rb | 15 +++++++++++++-- test/public/smtp-command-test.rb | 7 ++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/sisimai/smtp/command.rb b/lib/sisimai/smtp/command.rb index aaae99e2..abb186e9 100644 --- a/lib/sisimai/smtp/command.rb +++ b/lib/sisimai/smtp/command.rb @@ -8,14 +8,25 @@ class << self 'MAIL F', 'RCPT', 'RCPT T', 'DATA' ].freeze + # Check that an SMTP command in the argument is valid or not + # @param [String] argv0 An SMTP command + # @return [Boolean] 0: Is not a valid SMTP command, 1: Is a valid SMTP command + # @since v5.0.0 + def test(argv0 = '') + return nil if argv0.empty? + return nil if argv0.size < 4 + return true if %w[HELO EHLO MAIL RCPT DATA QUIT AUTH STARTTLS].any? { |a| argv0.include?(a) } + return true if argv0.include?('CONN') # CONN is a pseudo SMTP command used only in Sisimai + return false + end + # Pick an SMTP command from the given string # @param [String] argv0 A transcript text MTA returned # @return [String] An SMTP command # @return [undef] Failed to find an SMTP command or the 1st argument is missing # @since v5.0.0 def find(argv0 = '') - return nil unless argv0.size > 3 - return nil unless argv0 =~ /(?:HELO|EHLO|STARTTLS|AUTH|MAIL|RCPT|DATA)/ + return nil unless Sisimai::SMTP::Command.test(argv0) stringsize = argv0.size commandset = [] diff --git a/test/public/smtp-command-test.rb b/test/public/smtp-command-test.rb index b540b432..3004418b 100644 --- a/test/public/smtp-command-test.rb +++ b/test/public/smtp-command-test.rb @@ -2,7 +2,7 @@ require 'sisimai/smtp/command' class SMTPCommandTest < Minitest::Test - Methods = { class: %w[find] } + Methods = { class: %w[test find] } Strings = { 'HELO' => [ 'lost connection with mx.example.jp[192.0.2.2] while performing the HELO handshake', @@ -35,10 +35,13 @@ def test_methods end def test_code + assert_equal false, Sisimai::SMTP::Command.test("NEKO") + assert_nil Sisimai::SMTP::Command.test("") assert_nil Sisimai::SMTP::Command.find("") Strings.each_key do |e| assert_match /[A-Z]{4}/, e + assert_equal true, Sisimai::SMTP::Command.test(e) Strings[e].each do |ee| cv = Sisimai::SMTP::Command.find(ee) assert_instance_of String, cv @@ -47,6 +50,8 @@ def test_code end ce = assert_raises ArgumentError do + Sisimai::SMTP::Command.test() + Sisimai::SMTP::Command.test(nil, nil) Sisimai::SMTP::Command.find() Sisimai::SMTP::Command.find(nil, nil) end From d955659ce1b18cac52bb966602c1312c44701a7e Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sat, 18 Feb 2023 01:08:51 +0900 Subject: [PATCH 02/37] Implement Sisimai::SMTP::Reply.test and tests for the method --- lib/sisimai/smtp/reply.rb | 183 ++++++++++++++++++++++++--------- test/public/smtp-reply-test.rb | 12 ++- 2 files changed, 142 insertions(+), 53 deletions(-) diff --git a/lib/sisimai/smtp/reply.rb b/lib/sisimai/smtp/reply.rb index 4ed03cc3..02924318 100644 --- a/lib/sisimai/smtp/reply.rb +++ b/lib/sisimai/smtp/reply.rb @@ -1,64 +1,145 @@ # http://www.ietf.org/rfc/rfc5321.txt # 4.2.1. Reply Code Severities and Theory -# 2yz Positive Completion reply -# 3yz Positive Intermediate reply -# 4yz Transient Negative Completion reply -# 5yz Permanent Negative Completion reply # -# x0z Syntax: These replies refer to syntax errors, syntactically -# correct commands that do not fit any functional category, and -# unimplemented or superfluous commands. -# x1z Information: These are replies to requests for information, such -# as status or help. -# x2z Connections: These are replies referring to the transmission -# channel. -# x3z Unspecified. -# x4z Unspecified. -# x5z Mail system: These replies indicate the status of the receiver -# mail system vis-a-vis the requested transfer or other mail system -# action. +# There are four values for the first digit of the reply code: +# +# 2yz Positive Completion reply +# The requested action has been successfully completed. A new request may be initiated. +# +# 3yz Positive Intermediate reply +# The command has been accepted, but the requested action is being held in abeyance, pending +# receipt of further information. The SMTP client should send another command specifying this +# information. This reply is used in command sequence groups (i.e., in DATA). +# +# 4yz Transient Negative Completion reply +# The command was not accepted, and the requested action did not occur. However, the error +# condition is temporary, and the action may be requested again. The sender should return to +# the beginning of the command sequence (if any). It is difficult to assign a meaning to +# "transient" when two different sites (receiver- and sender-SMTP agents) must agree on the +# interpretation. Each reply in this category might have a different time value, but the SMTP +# client SHOULD try again. A rule of thumb to determine whether a reply fits into the 4yz or +# the 5yz category (see below) is that replies are 4yz if they can be successful if repeated +# without any change in command form or in properties of the sender or receiver (that is, the +# command is repeated identically and the receiver does not put up a new implementation). # -# 4.2.3. Reply Codes in Numeric Order -# 211 System status, or system help reply -# 214 Help message (Information on how to use the receiver or the -# meaning of a particular non-standard command; this reply is useful -# only to the human user) -# 220 Service ready -# 221 Service closing transmission channel -# 250 Requested mail action okay, completed -# 251 User not local; will forward to (See Section 3.4) -# 252 Cannot VRFY user, but will accept message and attempt delivery -# (See Section 3.5.3) -# 354 Start mail input; end with . -# 421 Service not available, closing transmission channel -# (This may be a reply to any command if the service knows it must -# shut down) -# 450 Requested mail action not taken: mailbox unavailable (e.g., -# mailbox busy or temporarily blocked for policy reasons) -# 451 Requested action aborted: local error in processing -# 452 Requested action not taken: insufficient system storage -# 455 Server unable to accommodate parameters -# 500 Syntax error, command unrecognized (This may include errors such -# as command line too long) -# 501 Syntax error in parameters or arguments -# 502 Command not implemented (see Section 4.2.4) -# 503 Bad sequence of commands -# 504 Command parameter not implemented -# 550 Requested action not taken: mailbox unavailable (e.g., mailbox -# not found, no access, or command rejected for policy reasons) -# 551 User not local; please try (See Section 3.4) -# 552 Requested mail action aborted: exceeded storage allocation -# 553 Requested action not taken: mailbox name not allowed (e.g., -# mailbox syntax incorrect) -# 554 Transaction failed (Or, in the case of a connection-opening -# response, "No SMTP service here") -# 555 MAIL FROM/RCPT TO parameters not recognized or not implemented +# 5yz Permanent Negative Completion reply +# The command was not accepted and the requested action did not occur. The SMTP client SHOULD +# NOT repeat the exact request (in the same sequence). Even some "permanent" error conditions +# can be corrected, so the human user may want to direct the SMTP client to reinitiate the +# command sequence by direct action at some point in the future (e.g., after the spelling has +# been changed, or the user has altered the account status). # +# The second digit encodes responses in specific categories: +# +# x0z Syntax: These replies refer to syntax errors, syntactically correct commands that do +# not fit any functional category, and unimplemented or superfluous commands. +# x1z Information: These are replies to requests for information, such as status or help. +# x2z Connections: These are replies referring to the transmission channel. +# x3z Unspecified. +# x4z Unspecified. +# x5z Mail system: These replies indicate the status of the receiver mail system vis-a-vis +# the requested transfer or other mail system action. + module Sisimai module SMTP # Sisimai::SMTP::Reply is utilities for getting SMTP Reply Code value from error message text. module Reply class << self + ReplyCode2 = [ + # http://www.ietf.org/rfc/rfc5321.txt + # 211 System status, or system help reply + # 214 Help message (Information on how to use the receiver or the meaning of a particular + # non-standard command; this reply is useful only to the human user) + # 220 Service ready + # 221 Service closing transmission channel + # 235 Authentication successful (See RFC2554) + # 250 Requested mail action okay, completed + # 251 User not local; will forward to (See Section 3.4) + # 252 Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3) + # 253 OK, pending messages for node started (See RFC1985) + # 354 Start mail input; end with . + 211, 214, 220, 221, 235, 250, 251, 252, 253, 354 + ].freeze + ReplyCode4 = [ + # 421 Service not available, closing transmission channel (This may be a reply + # to any command if the service knows it must shut down) + # 422 (See RFC5248) + # 430 (See RFC5248) + # 432 A password transition is needed (See RFC4954) + # 450 Requested mail action not taken: mailbox unavailable (e.g., mailbox busy or temporarily + # blocked for policy reasons) + # 451 Requested action aborted: local error in processing + # 452 Requested action not taken: insufficient system storage + # 453 You have no mail (See RFC2645) + # 454 Temporary authentication failure (See RFC4954) + # 455 Server unable to accommodate parameters + # 456 please retry immediately the message over IPv4 because it fails SPF and DKIM (See + # https://datatracker.ietf.org/doc/html/draft-martin-smtp-ipv6-to-ipv4-fallback-00 + # 458 Unable to queue messages for node (See RFC1985) + # 459 Node not allowed: (See RFC51985) + 421, 450, 451, 452, 422, 430, 432, 453, 454, 455, 456, 458, 459 + ].freeze + ReplyCode5 = [ + # 500 Syntax error, command unrecognized (This may include errors such as command line too long) + # 501 Syntax error in parameters or arguments + # 502 Command not implemented (see Section 4.2.4) + # 503 Bad sequence of commands + # 504 Command parameter not implemented + # 520 Please use the correct QHLO ID (See https://datatracker.ietf.org/doc/id/draft-fanf-smtp-quickstart-01.txt) + # 521 Host does not accept mail (See RFC7504) + # 523 Encryption Needed (See RFC5248) + # 524 (See RFC5248) + # 525 User Account Disabled (See RFC5248) + # 530 Authentication required (See RFC4954) + # 533 (See RFC5248) + # 534 Authentication mechanism is too weak (See RFC4954) + # 535 Authentication credentials invalid (See RFC4954) + # 538 Encryption required for requested authentication mechanism (See RFC4954) + # 550 Requested action not taken: mailbox unavailable (e.g., mailbox not found, no access, or + # command rejected for policy reasons) + # 551 User not local; please try (See Section 3.4) + # 552 Requested mail action aborted: exceeded storage allocation + # 553 Requested action not taken: mailbox name not allowed (e.g., mailbox syntax incorrect) + # 554 Transaction failed (Or, in the case of a connection-opening response, "No SMTP service here") + # 555 MAIL FROM/RCPT TO parameters not recognized or not implemented + # 556 Domain does not accept mail (See RFC7504) + 550, 552, 553, 551, 521, 525, 502, 520, 523, 524, 530, 533, 534, 535, 538, 551, 555, 556, + 554, 500, 501, 502, 503, 504, + ].freeze + CodeOfSMTP = { '2' => ReplyCode2, '4' => ReplyCode4, '5' => ReplyCode5 }.freeze + + # Check whether a reply code is a valid code or not + # @param [String] argv1 Reply Code(DSN) + # @return [Boolean] 0 = Invalid reply code, 1 = Valid reply code + # @see code + # @since v5.0.0 + def test(argv1 = '') + return nil if argv1.empty? + + reply = argv1.to_i + first = (reply / 100).to_i + + return false if reply < 200 + return false if reply > 599 + return false if reply % 100 > 59 + + if first == 2 + # 2yz + return false if reply < 211 + return false if reply > 252 + return false if reply > 221 && reply < 250 + return true + end + + if first == 3 + # 3yz + return false unless reply == 354 + return true + end + + return true + end + IP4Re = %r{\b (?:\d|[01]?\d\d|2[0-4]\d|25[0-5])[.] (?:\d|[01]?\d\d|2[0-4]\d|25[0-5])[.] diff --git a/test/public/smtp-reply-test.rb b/test/public/smtp-reply-test.rb index 6fb6ac25..3b85e897 100644 --- a/test/public/smtp-reply-test.rb +++ b/test/public/smtp-reply-test.rb @@ -2,7 +2,7 @@ require 'sisimai/smtp/reply' class SMTPReply< Minitest::Test - Methods = { class: %w[find] } + Methods = { class: %w[test find] } Message = [ 'smtp; 250 2.1.5 Ok', 'smtp; 550 5.1.1 ... User Unknown', @@ -30,7 +30,7 @@ class SMTPReply< Minitest::Test 'SMTP; 550 5.1.1 ... User Unknown', "smtp; 550 'arathib@vnet.IBM.COM' is not a", 'smtp; 550 user unknown', - 'smtp; 426 connection timed out', + 'smtp; 421 connection timed out', 'smtp;550 5.2.1 ... User Unknown', 'smtp; 550 5.7.1 Message content rejected, UBE, id=00000-00-000', '550 5.1.1 sid=i01K1n00l0kn1Em01 Address rejected foobar@foobar.com. [code=28] ', @@ -86,11 +86,19 @@ def test_methods Methods[:class].each { |e| assert_respond_to Sisimai::SMTP::Reply, e } end + def test_test + assert_nil Sisimai::SMTP::Reply.test() + %w[101 192 270 386 499 567 640 727].each do |e| + assert_equal false, Sisimai::SMTP::Reply.test(e) + end + end + def test_find Message.each do |e| cv = Sisimai::SMTP::Reply.find(e) assert_instance_of String, cv assert_match /\A[245][0-5][0-9]\z/, cv + assert_equal true, Sisimai::SMTP::Reply.test(cv) end ce = assert_raises ArgumentError do From e8ba6e05e2891ad473c9b821b020c35c2ac580b0 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sat, 18 Feb 2023 15:15:09 +0900 Subject: [PATCH 03/37] Implement Sisimai::String.aligned() and tests for the method --- lib/sisimai/string.rb | 25 +++++++++++++++++++++++++ test/public/string-test.rb | 16 +++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/sisimai/string.rb b/lib/sisimai/string.rb index e91dc08e..08574693 100644 --- a/lib/sisimai/string.rb +++ b/lib/sisimai/string.rb @@ -51,6 +51,31 @@ def sweep(argv1) return argv1 end + # Check if each element of the 2nd argument is aligned in the 1st argument or not + # @param [String] argv1 String to be checked + # @param [Array] argv2 List including the ordered strings + # @return [Bool] 0, 1 + # @since v5.0.0 + def aligned(argv1, argv2) + return nil if argv1.to_s.empty? + return nil unless argv2.is_a? Array + return nil unless argv2.size > 1 + + align = -1 + right = 0 + + argv2.each do |e| + # Get the position of each element in the 1st argument using index() + p = argv1.index(e, align + 1) + break unless p # Break this loop when there is no string in the 1st argument + align = e.length + p - 1 # There is an aligned string in the 1st argument + right += 1 + end + + return true if right == argv2.size + return false + end + # Convert given HTML text to plain text # @param [String] argv1 HTML text # @param [Boolean] loose Loose check flag diff --git a/test/public/string-test.rb b/test/public/string-test.rb index 98343df6..da136ad8 100644 --- a/test/public/string-test.rb +++ b/test/public/string-test.rb @@ -2,7 +2,7 @@ require 'sisimai/string' class StringTest < Minitest::Test - Methods = { class: %w[token is_8bit sweep to_plain to_utf8] } + Methods = { class: %w[token is_8bit sweep aligned to_plain to_utf8] } def test_methods Methods[:class].each { |e| assert_respond_to Sisimai::String, e } @@ -11,6 +11,8 @@ def test_methods Es = 'envelope-sender@example.jp' Er = 'envelope-recipient@example.org' Ts = '239aa35547613b2fa94f40c7f35f4394e99fdd88' + Fr = 'Final-Recipient: rfc822; ' + def test_token cv = Sisimai::String.token(Es, Er, 1) assert_instance_of String, cv @@ -53,6 +55,18 @@ def test_sweep assert_match /wrong number of arguments/, ce.to_s end + def test_aligned + assert_nil Sisimai::String.aligned(nil, nil) + assert_equal true, Sisimai::String.aligned(Fr, ['rfc822', ' <', '@', '>']) + assert_equal false, Sisimai::String.aligned(Fr, ['rfc822', '<<', ' ', '>']) + + ce = assert_raises ArgumentError do + Sisimai::String.aligned() + Sisimai::String.aligned(nil) + Sisimai::String.aligned("nekochan", [], 1) + end + end + Ht1 = ' From 7880489f59b486f43c0f5b0c6ddb6ffe6cae25d5 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sun, 19 Feb 2023 05:32:31 +0900 Subject: [PATCH 04/37] Implement Sisimai::String.ipv4() and tests for the method --- lib/sisimai/string.rb | 46 ++++++++++++++++++++++++++++++++++++++ test/public/string-test.rb | 14 ++++++++++++ 2 files changed, 60 insertions(+) diff --git a/lib/sisimai/string.rb b/lib/sisimai/string.rb index 08574693..67bade37 100644 --- a/lib/sisimai/string.rb +++ b/lib/sisimai/string.rb @@ -76,6 +76,52 @@ def aligned(argv1, argv2) return false end + # Find an IPv4 address from the given string + # @param [String] argv1 String including an IPv4 address + # @return [Array] List of IPv4 addresses + # @since v5.0.0 + def ipv4(argv0) + return nil if argv0.to_s.empty? + return [] if argv0.size < 7 + + ipv4a = [] + %w|( ) [ ]|.each do |e| + # Rewrite: "mx.example.jp[192.0.2.1]" => "mx.example.jp 192.0.2.1" + p0 = argv0.index(e); next unless p0 + argv0[p0, 1] = ' ' + end + + argv0.split(' ').each do |e| + # Find string including an IPv4 address + next unless e.index('.') # IPv4 address must include "." character + + lx = e.size; next if lx < 7 || lx > 17 # 0.0.0.0 = 7, [255.255.255.255] = 17 + cu = 0 # Cursor for seeking each octet of an IPv4 address + as = '' # ASCII Code of each character + eo = '' # Buffer of each octet of IPv4 Address + + while cu < lx + # Check whether each character is a number or "." or not + as = e[cu, 1].ord + cu += 1 + + if as < 48 || as > 57 + # The character is not a number(0-9) + break if as != 46 # The character is not "." + next if eo == '' # The current buffer is empty + break if eo.to_i > 255 # The current buffer is greater than 255 + eo = '' + next + end + eo << as.chr + break if eo.to_i > 255 + end + ipv4a << e if eo.size > 0 && eo.to_i < 256 + end + + return ipv4a + end + # Convert given HTML text to plain text # @param [String] argv1 HTML text # @param [Boolean] loose Loose check flag diff --git a/test/public/string-test.rb b/test/public/string-test.rb index da136ad8..b197210b 100644 --- a/test/public/string-test.rb +++ b/test/public/string-test.rb @@ -67,6 +67,20 @@ def test_aligned end end + def test_ipv4 + ip4address = [ + ['host smtp.example.jp 127.0.0.4 SMTP error from remote mail server', '127.0.0.4'], + ['mx.example.jp (192.0.2.2) reason: 550 5.2.0 Mail rejete.', '192.0.2.2'], + ['Client host [192.0.2.49] blocked using cbl.abuseat.org (state 13).', '192.0.2.49'], + ['127.0.0.1', '127.0.0.1'], + ['365.31.7.1', nil], + ['a.b.c.d', nil], + ] + ip4address.each do |e| + assert_equal e[1], Sisimai::String.ipv4(e[0]).shift + end + end + Ht1 = ' From 2ff5d1683e204990c0b88815b62ec3729ee768ec Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sun, 19 Feb 2023 05:33:53 +0900 Subject: [PATCH 05/37] Sisimai::SMTP::Reply.find() borned again --- lib/sisimai/smtp/reply.rb | 52 ++++++++++++++++++++-------------- test/public/smtp-reply-test.rb | 2 +- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/lib/sisimai/smtp/reply.rb b/lib/sisimai/smtp/reply.rb index 02924318..1415a18f 100644 --- a/lib/sisimai/smtp/reply.rb +++ b/lib/sisimai/smtp/reply.rb @@ -58,7 +58,7 @@ class << self # 252 Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3) # 253 OK, pending messages for node started (See RFC1985) # 354 Start mail input; end with . - 211, 214, 220, 221, 235, 250, 251, 252, 253, 354 + '211', '214', '220', '221', '235', '250', '251', '252', '253', '354' ].freeze ReplyCode4 = [ # 421 Service not available, closing transmission channel (This may be a reply @@ -77,7 +77,7 @@ class << self # https://datatracker.ietf.org/doc/html/draft-martin-smtp-ipv6-to-ipv4-fallback-00 # 458 Unable to queue messages for node (See RFC1985) # 459 Node not allowed: (See RFC51985) - 421, 450, 451, 452, 422, 430, 432, 453, 454, 455, 456, 458, 459 + '421', '450', '451', '452', '422', '430', '432', '453', '454', '455', '456', '458', '459' ].freeze ReplyCode5 = [ # 500 Syntax error, command unrecognized (This may include errors such as command line too long) @@ -103,8 +103,8 @@ class << self # 554 Transaction failed (Or, in the case of a connection-opening response, "No SMTP service here") # 555 MAIL FROM/RCPT TO parameters not recognized or not implemented # 556 Domain does not accept mail (See RFC7504) - 550, 552, 553, 551, 521, 525, 502, 520, 523, 524, 530, 533, 534, 535, 538, 551, 555, 556, - 554, 500, 501, 502, 503, 504, + '550', '552', '553', '551', '521', '525', '502', '520', '523', '524', '530', '533', '534', + '535', '538', '551', '555', '556', '554', '500', '501', '502', '503', '504', ].freeze CodeOfSMTP = { '2' => ReplyCode2, '4' => ReplyCode4, '5' => ReplyCode5 }.freeze @@ -140,32 +140,40 @@ def test(argv1 = '') return true end - IP4Re = %r{\b - (?:\d|[01]?\d\d|2[0-4]\d|25[0-5])[.] - (?:\d|[01]?\d\d|2[0-4]\d|25[0-5])[.] - (?:\d|[01]?\d\d|2[0-4]\d|25[0-5])[.] - (?:\d|[01]?\d\d|2[0-4]\d|25[0-5]) - \b}x - # Get SMTP Reply Code from the given string # @param [String] argv1 String including SMTP Reply Code like 550 + # @param [String] argv2 Status code like 5.1.1 or 2 or 4 or 5 # @return [String] SMTP Reply Code # [Nil] The first argument did not include SMTP Reply Code value - def find(argv1 = nil) - return nil unless argv1 - return nil if argv1.empty? + def find(argv1 = '', argv2 = 'x') + return nil if argv1.to_s.size < 4 return nil if argv1.upcase.include?('X-UNIX') - # Convert found IPv4 addresses to '***.***.***.***' to avoid that the following code - # detects an octet of the IPv4 adress as an SMTP reply code. - argv1 = argv1.gsub(/#{IP4Re}/, '***.***.***.***') if argv1 =~ IP4Re + statuscode = argv2[0, 1] + replycodes = if statuscode == '5' || statuscode == '4' || statuscode == '2' + CodeOfSMTP[statuscode] + else + [*CodeOfSMTP['5'], *CodeOfSMTP['4'], *CodeOfSMTP['2']] + end + esmtperror = ' ' + argv1 + esmtpreply = '' # SMTP Reply Code + replyindex = 0 # A position of SMTP reply code found by the index() + formerchar = 0 # a character that is one character before the SMTP reply code + latterchar = 0 # a character that is one character after the SMTP reply code - if cv = argv1.match(/\b([45][0-7][0-9])\b/) || argv1.match(/\b(25[0-3])\b/) - # 550, 447, or 250 - return cv[1] - else - return nil + replycodes.each do |e| + # Try to find an SMTP Reply Code from the given string + replyindex = esmtperror.index(e); next unless replyindex + formerchar = esmtperror[replyindex - 1, 1].ord || 0 + lattercahr = esmtperror[replyindex + 3, 1].ord || 0 + + next if formerchar > 45 && formerchar < 58 + next if latterchar > 45 && latterchar < 58 + esmtpreply = e + break end + + return esmtpreply end end diff --git a/test/public/smtp-reply-test.rb b/test/public/smtp-reply-test.rb index 3b85e897..55857786 100644 --- a/test/public/smtp-reply-test.rb +++ b/test/public/smtp-reply-test.rb @@ -103,7 +103,7 @@ def test_find ce = assert_raises ArgumentError do Sisimai::SMTP::Reply.find() - Sisimai::SMTP::Reply.find(nil, nil) + Sisimai::SMTP::Reply.find(nil, nil, nil) end assert_nil Sisimai::SMTP::Reply.find('') end From 9ee9600e2eed27a6e27f151e05583c0daf1b3d0a Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sun, 19 Feb 2023 05:49:36 +0900 Subject: [PATCH 06/37] Register "4.4.8" as "networkerror" --- lib/sisimai/smtp/status.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/sisimai/smtp/status.rb b/lib/sisimai/smtp/status.rb index 547a01d7..2d2be452 100644 --- a/lib/sisimai/smtp/status.rb +++ b/lib/sisimai/smtp/status.rb @@ -1,5 +1,4 @@ # http://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xhtml -# # ------------------------------------------------------------------------------------------------- # [Class Sub-Codes] # 2.X.Y Success @@ -8,7 +7,6 @@ # # ------------------------------------------------------------------------------------------------- # [Subject Sub-Codes] -# # X.0.X --- Other or Undefined Status # There is no additional subject information available. # @@ -53,7 +51,6 @@ # # ------------------------------------------------------------------------------------------------- # [Enumerated Status Codes] -# # X.0.0 Any Other undefined Status:(RFC 3463) # Other undefined status is the only undefined error code. It should be used for all # errors for which only the class of the error is known. @@ -201,6 +198,13 @@ # remained on that host too long or because the time-to-live value specified by the # sender of the message was exceeded. If possible, the code for the actual problem # found when delivery was attempted should be returned rather than this code. + +# X.4.8 421 Retry on IPv4 +# 451 the mail system will not accept this message over IPv6 because it lacks some re- +# 456 quirments described in the full text of the rejection, however the sending mail +# system can retry immediately to submit the message over IPv4 only. +# https://datatracker.ietf.org/doc/html/draft-martin-smtp-ipv6-to-ipv4-fallback-00 +# # ------------------------------------------------------------------------------------------------- # X.5.0 220 Other or undefined protocol status:(RFC 3463) # 250-253 Something was wrong with the protocol necessary to deliver the message to the next @@ -446,7 +450,6 @@ # (e.g., because it could not return a DSN). # ------------------------------------------------------------------------------------------------- # SAMPLES -# # 554 5.5.0 No recipients have been specified # 503 5.5.0 Valid RCPT TO required before BURL # 554 5.6.3 Conversion required but not supported @@ -506,7 +509,8 @@ class << self '4.4.5' => 'systemfull', # Mail system congestion '4.4.6' => 'networkerror', # Routing loop detected '4.4.7' => 'expired', # Delivery time expired - # '4.5.0' => 'networkerror', # Other or undefined protocol status + '4.4.8' => 'networkerror', # Retry on IPv4 +# '4.5.0' => 'networkerror', # Other or undefined protocol status '4.5.3' => 'systemerror', # Too many recipients '4.5.5' => 'systemerror', # Wrong protocol version '4.6.0' => 'contenterror', # Other or undefined media error From 0c7b35275ce3aa57edfd5b1bbf3886dc54af24f8 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sun, 19 Feb 2023 06:22:37 +0900 Subject: [PATCH 07/37] Update test to avoid "DEPRECATED: Use assert_nil if expecting nil" message --- test/public/string-test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/public/string-test.rb b/test/public/string-test.rb index b197210b..b2c8175d 100644 --- a/test/public/string-test.rb +++ b/test/public/string-test.rb @@ -73,11 +73,11 @@ def test_ipv4 ['mx.example.jp (192.0.2.2) reason: 550 5.2.0 Mail rejete.', '192.0.2.2'], ['Client host [192.0.2.49] blocked using cbl.abuseat.org (state 13).', '192.0.2.49'], ['127.0.0.1', '127.0.0.1'], - ['365.31.7.1', nil], - ['a.b.c.d', nil], + ['365.31.7.1', ''], + ['a.b.c.d', ''], ] ip4address.each do |e| - assert_equal e[1], Sisimai::String.ipv4(e[0]).shift + assert_equal e[1], Sisimai::String.ipv4(e[0]).shift.to_s end end From 09386334e2597e207c60f03439b1ba9ad7ed928d Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sun, 19 Feb 2023 06:24:15 +0900 Subject: [PATCH 08/37] Update the list of bounce reasons --- test/public/smtp-status-test.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/public/smtp-status-test.rb b/test/public/smtp-status-test.rb index 456e1fb1..1a157167 100644 --- a/test/public/smtp-status-test.rb +++ b/test/public/smtp-status-test.rb @@ -2,13 +2,13 @@ require 'sisimai/smtp/status' class SMTPStatusTest < Minitest::Test - Methods = { class: %w[code name find] } - Reasons = [ - 'blocked', 'contenterror', 'exceedlimit', 'expired', 'filtered', 'hasmoved', 'hostunknown', - 'mailboxfull', 'mailererror', 'mesgtoobig', 'networkerror', 'norelaying', 'notaccept', - 'onhold', 'rejected', 'securityerror', 'spamdetected', 'suspend', 'systemerror', 'systemfull', - 'toomanyconn', 'userunknown', 'syntaxerror', - ] + Methods = { class: %w[code name test find] } + Reasons = %w[ + authfailure badreputation blocked contenterror exceedlimit expired filtered hasmoved + hostunknown mailboxfull mailererror mesgtoobig networkerror notaccept onhold rejected + norelaying spamdetected virusdetected policyviolation securityerror speeding suspend + systemerror systemfull toomanyconn userunknown syntaxerror + ] CodeSet = %w[ 2.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.1 4.2.2 4.2.3 4.2.4 4.3.1 4.3.2 4.3.3 4.3.5 From 19e7706b7216a380b30593545bb6e1bc04908f48 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sun, 19 Feb 2023 06:41:14 +0900 Subject: [PATCH 09/37] Update test for ArgumentError --- test/public/smtp-reply-test.rb | 4 ++++ test/public/string-test.rb | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/test/public/smtp-reply-test.rb b/test/public/smtp-reply-test.rb index 55857786..02e427e5 100644 --- a/test/public/smtp-reply-test.rb +++ b/test/public/smtp-reply-test.rb @@ -91,6 +91,10 @@ def test_test %w[101 192 270 386 499 567 640 727].each do |e| assert_equal false, Sisimai::SMTP::Reply.test(e) end + + ce = assert_raises ArgumentError do + Sisimai::SMTP::Reply.test(nil, nil, nil) + end end def test_find diff --git a/test/public/string-test.rb b/test/public/string-test.rb index b2c8175d..aaec3670 100644 --- a/test/public/string-test.rb +++ b/test/public/string-test.rb @@ -79,6 +79,11 @@ def test_ipv4 ip4address.each do |e| assert_equal e[1], Sisimai::String.ipv4(e[0]).shift.to_s end + + ce = assert_raises ArgumentError do + Sisimai::String.ipv4() + Sisimai::String.ipv4("nekochan", nil) + end end Ht1 = ' From c322142b31f582b6b531b00a7c5ff3b43956911d Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sun, 19 Feb 2023 06:52:33 +0900 Subject: [PATCH 10/37] Implement Sisimai::SMTP::Status.test() and tests for the method --- lib/sisimai/smtp/status.rb | 24 +++++++++++++++++++++++- test/public/smtp-status-test.rb | 11 +++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/sisimai/smtp/status.rb b/lib/sisimai/smtp/status.rb index 2d2be452..a2adf178 100644 --- a/lib/sisimai/smtp/status.rb +++ b/lib/sisimai/smtp/status.rb @@ -679,10 +679,32 @@ def code(argv1 = nil, argv2 = false) # @see code def name(argv1 = nil) return nil unless argv1 - return nil unless argv1 =~ /\A[245][.]\d[.]\d+\z/ + return nil unless Sisimai::SMTP::Status.test(argv1) return StandardCode[argv1] || nil end + # Check whether a status code is a valid code or not + # @param [String] argv1 Status code(DSN) + # @return [Boolean] 0 = Invalid status code, 1 = Valid status code + # @see code + # @since v5.0.0 + def test(argv1 = '') + return nil if argv1.to_s.empty? + return false if argv1.size < 5 + return false if argv1.size > 7 + + token = [] + argv1.split('.').each { |e| token << e.to_i } + return false unless token.size == 3 + return false if token[0] < 2 + return false if token[0] == 3 + return false if token[0] > 5 + return false if token[1] < 0 + return false if token[1] > 7 + return false if token[2] < 0 + return true + end + # Get a DSN code value from given string including DSN # @param [String] argv1 String including DSN # @return [String, Nil] DSN or Nil if the first agument did not include DSN diff --git a/test/public/smtp-status-test.rb b/test/public/smtp-status-test.rb index 1a157167..9f999b8e 100644 --- a/test/public/smtp-status-test.rb +++ b/test/public/smtp-status-test.rb @@ -73,11 +73,22 @@ def test_name assert_nil Sisimai::SMTP::Status.name('') end + def test_test + assert_nil Sisimai::SMTP::Status.test('') + assert_equal false, Sisimai::SMTP::Status.test('3.14') + + ce = assert_raises ArgumentError do + Sisimai::SMTP::Status.test() + Sisimai::SMTP::Status.test(nil, nil) + end + end + def test_find Message.each do |e| cv = Sisimai::SMTP::Status.find(e) assert_instance_of String, cv assert_match /\A[245][.]\d+[.]\d\z/, cv + assert_equal true, Sisimai::SMTP::Status.test(cv) end ce = assert_raises ArgumentError do From 82b95ec1aabebaa03976cf4ba9cd3d0d95f38dcd Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sat, 4 Mar 2023 18:42:37 +0900 Subject: [PATCH 11/37] Fix code for checking the value of "f" returned from Sisimai::RFC1894.match() --- lib/sisimai/lhost/amavis.rb | 2 +- lib/sisimai/lhost/amazonses.rb | 2 +- lib/sisimai/lhost/amazonworkmail.rb | 2 +- lib/sisimai/lhost/aol.rb | 2 +- lib/sisimai/lhost/barracuda.rb | 2 +- lib/sisimai/lhost/bigfoot.rb | 2 +- lib/sisimai/lhost/courier.rb | 2 +- lib/sisimai/lhost/domino.rb | 2 +- lib/sisimai/lhost/facebook.rb | 2 +- lib/sisimai/lhost/gsuite.rb | 2 +- lib/sisimai/lhost/messagelabs.rb | 2 +- lib/sisimai/lhost/office365.rb | 2 +- lib/sisimai/lhost/outlook.rb | 2 +- lib/sisimai/lhost/postfix.rb | 45 ++++++++++------------------- lib/sisimai/lhost/powermta.rb | 2 +- lib/sisimai/lhost/receivingses.rb | 2 +- lib/sisimai/lhost/sendgrid.rb | 2 +- lib/sisimai/lhost/sendmail.rb | 2 +- lib/sisimai/lhost/yandex.rb | 2 +- 19 files changed, 34 insertions(+), 47 deletions(-) diff --git a/lib/sisimai/lhost/amavis.rb b/lib/sisimai/lhost/amavis.rb index 3c92bbd1..cd4a1c83 100644 --- a/lib/sisimai/lhost/amavis.rb +++ b/lib/sisimai/lhost/amavis.rb @@ -132,7 +132,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end end diff --git a/lib/sisimai/lhost/amazonses.rb b/lib/sisimai/lhost/amazonses.rb index cd751763..1bffa263 100644 --- a/lib/sisimai/lhost/amazonses.rb +++ b/lib/sisimai/lhost/amazonses.rb @@ -280,7 +280,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/amazonworkmail.rb b/lib/sisimai/lhost/amazonworkmail.rb index bf2df86d..8d0982a6 100644 --- a/lib/sisimai/lhost/amazonworkmail.rb +++ b/lib/sisimai/lhost/amazonworkmail.rb @@ -83,7 +83,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end end diff --git a/lib/sisimai/lhost/aol.rb b/lib/sisimai/lhost/aol.rb index 8c7bfe20..12bd499a 100644 --- a/lib/sisimai/lhost/aol.rb +++ b/lib/sisimai/lhost/aol.rb @@ -86,7 +86,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/barracuda.rb b/lib/sisimai/lhost/barracuda.rb index df8e09ef..e720352c 100644 --- a/lib/sisimai/lhost/barracuda.rb +++ b/lib/sisimai/lhost/barracuda.rb @@ -71,7 +71,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end end diff --git a/lib/sisimai/lhost/bigfoot.rb b/lib/sisimai/lhost/bigfoot.rb index 34cefcc5..a29c282d 100644 --- a/lib/sisimai/lhost/bigfoot.rb +++ b/lib/sisimai/lhost/bigfoot.rb @@ -79,7 +79,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/courier.rb b/lib/sisimai/lhost/courier.rb index b14089bb..c910ce77 100644 --- a/lib/sisimai/lhost/courier.rb +++ b/lib/sisimai/lhost/courier.rb @@ -97,7 +97,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/domino.rb b/lib/sisimai/lhost/domino.rb index f0f3e25c..5d2049e7 100644 --- a/lib/sisimai/lhost/domino.rb +++ b/lib/sisimai/lhost/domino.rb @@ -107,7 +107,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/facebook.rb b/lib/sisimai/lhost/facebook.rb index 6b37347d..e72eef19 100644 --- a/lib/sisimai/lhost/facebook.rb +++ b/lib/sisimai/lhost/facebook.rb @@ -133,7 +133,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/gsuite.rb b/lib/sisimai/lhost/gsuite.rb index 60c4b099..5b766685 100644 --- a/lib/sisimai/lhost/gsuite.rb +++ b/lib/sisimai/lhost/gsuite.rb @@ -86,7 +86,7 @@ def inquire(mhead, mbody) v['lhost'] = '' if v['lhost'].include?('@') end - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/messagelabs.rb b/lib/sisimai/lhost/messagelabs.rb index 6e4d4820..69417104 100644 --- a/lib/sisimai/lhost/messagelabs.rb +++ b/lib/sisimai/lhost/messagelabs.rb @@ -84,7 +84,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/office365.rb b/lib/sisimai/lhost/office365.rb index c07d8ee5..488f9ab7 100644 --- a/lib/sisimai/lhost/office365.rb +++ b/lib/sisimai/lhost/office365.rb @@ -183,7 +183,7 @@ def inquire(mhead, mbody) next if o[0] =~ /\A(?:diagnostic-code|final-recipient)\z/ v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] else # Capture "Diagnostic-Code:" field because no error messages have been captured diff --git a/lib/sisimai/lhost/outlook.rb b/lib/sisimai/lhost/outlook.rb index b51cc5fa..9dd19e1d 100644 --- a/lib/sisimai/lhost/outlook.rb +++ b/lib/sisimai/lhost/outlook.rb @@ -83,7 +83,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/postfix.rb b/lib/sisimai/lhost/postfix.rb index 060f87a8..82b7f838 100644 --- a/lib/sisimai/lhost/postfix.rb +++ b/lib/sisimai/lhost/postfix.rb @@ -4,30 +4,20 @@ module Sisimai::Lhost module Postfix class << self require 'sisimai/lhost' + require 'sisimai/smtp/command' # Postfix manual - bounce(5) - http://www.postfix.org/bounce.5.html Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'].freeze - MarkingsOf = { - message: %r{\A(?> - [ ]+The[ ](?: - Postfix[ ](?: - program\z # The Postfix program - |on[ ].+[ ]program\z # The Postfix on program - ) - |\w+[ ]Postfix[ ]program\z # The Postfix program - |mail[ ]system\z # The mail system - |\w+[ ]program\z # The program - ) - |This[ ]is[ ]the[ ](?: - Postfix[ ]program # This is the Postfix program - |\w+[ ]Postfix[ ]program # This is the Postfix program - |\w+[ ]program # This is the Postfix program - |mail[ ]system[ ]at[ ]host # This is the mail system at host . - ) - ) - }x, - # :from => %r/ [(]Mail Delivery System[)]\z/, + StartingOf = { + # Postfix manual - bounce(5) - http://www.postfix.org/bounce.5.html + message: [ + ['The ', 'Postfix '], # The Postfix program, The Postfix on program + ['The ', 'mail system'], # The mail system + ['The ', 'program'], # The pogram + ['This is the', 'Postfix'], # This is the Postfix program + ['This is the', 'mail system'], # This is the mail system at host + ], }.freeze # Parse bounce messages from Postfix @@ -51,9 +41,6 @@ def inquire(mhead, mbody) return nil unless match return nil if mhead['x-aol-ip'] - require 'sisimai/rfc1894' - require 'sisimai/address' - require 'sisimai/smtp/command' permessage = {} # (Hash) Store values of each Per-Message field dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) @@ -78,7 +65,7 @@ def inquire(mhead, mbody) v ||= dscontents[-1] p = e['response'] - if e['command'] =~ /\A(?:HELO|EHLO)/ + if e['command'] == 'HELO' || e['command'] == 'EHLO' # Use the argument of EHLO/HELO command as a value of "lhost" v['lhost'] = e['argument'] @@ -114,7 +101,7 @@ def inquire(mhead, mbody) if readcursor == 0 # Beginning of the bounce message or message/delivery-status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + readcursor |= Indicators[:deliverystatus] if StartingOf[:message].any? { |a| Sisimai::String.aligned(e, a) } next end next if (readcursor & Indicators[:deliverystatus]) == 0 @@ -151,7 +138,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else @@ -204,9 +191,9 @@ def inquire(mhead, mbody) else # Get error message continued from the previous line next unless anotherset['diagnosis'] - if e =~ /\A[ ]{4}(.+)\z/ + if e.start_with?(' ') # host mx.example.jp said:... - anotherset['diagnosis'] << ' ' << e + anotherset['diagnosis'] << ' ' << e[4, e.size] end end end @@ -281,7 +268,7 @@ def inquire(mhead, mbody) e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || '' e['command'] = commandset.shift || Sisimai::SMTP::Command.find(e['diagnosis']) e['command'] ||= 'HELO' if e['diagnosis'].include?('refused to talk to me:') - e['spec'] ||= 'SMTP' if e['diagnosis'] =~ /host .+ said:/ + e['spec'] ||= 'SMTP' if Sisimai::String.aligned(e['diagnosis'], ['host ', ' said:']) end return { 'ds' => dscontents, 'rfc822' => emailparts[1] } diff --git a/lib/sisimai/lhost/powermta.rb b/lib/sisimai/lhost/powermta.rb index 93a65a75..cc893f53 100644 --- a/lib/sisimai/lhost/powermta.rb +++ b/lib/sisimai/lhost/powermta.rb @@ -81,7 +81,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/receivingses.rb b/lib/sisimai/lhost/receivingses.rb index 045ac7b1..8508c2e2 100644 --- a/lib/sisimai/lhost/receivingses.rb +++ b/lib/sisimai/lhost/receivingses.rb @@ -82,7 +82,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/sendgrid.rb b/lib/sisimai/lhost/sendgrid.rb index 1098b255..580a8227 100644 --- a/lib/sisimai/lhost/sendgrid.rb +++ b/lib/sisimai/lhost/sendgrid.rb @@ -94,7 +94,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/sendmail.rb b/lib/sisimai/lhost/sendmail.rb index 49e6c527..7e5aa731 100644 --- a/lib/sisimai/lhost/sendmail.rb +++ b/lib/sisimai/lhost/sendmail.rb @@ -89,7 +89,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else diff --git a/lib/sisimai/lhost/yandex.rb b/lib/sisimai/lhost/yandex.rb index 38718550..abb9eb08 100644 --- a/lib/sisimai/lhost/yandex.rb +++ b/lib/sisimai/lhost/yandex.rb @@ -82,7 +82,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else From a128154b967c744a367dcd45d786376e48d37e21 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sat, 4 Mar 2023 18:42:54 +0900 Subject: [PATCH 12/37] Fix code for checking the value of "f" returned from Sisimai::RFC1894.match() --- lib/sisimai/rfc3464.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sisimai/rfc3464.rb b/lib/sisimai/rfc3464.rb index f07fa4e0..c788be02 100644 --- a/lib/sisimai/rfc3464.rb +++ b/lib/sisimai/rfc3464.rb @@ -198,7 +198,7 @@ def inquire(mhead, mbody) next unless fieldtable[o[0]] v[fieldtable[o[0]]] = o[2] - next unless f == 1 + next unless f permessage[fieldtable[o[0]]] = o[2] end else From dc69da70081a7357ae0bb6c48524e34f71331212 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sun, 5 Mar 2023 15:14:57 +0900 Subject: [PATCH 13/37] Import updated test results --- test/public/rhost-franceptt.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/rhost-franceptt.rb b/test/public/rhost-franceptt.rb index 4dde843a..e420bac1 100644 --- a/test/public/rhost-franceptt.rb +++ b/test/public/rhost-franceptt.rb @@ -10,7 +10,7 @@ module FrancePTT '06' => [['4.0.0', '', 'blocked', false]], '07' => [['4.0.0', '421', 'blocked', false]], '08' => [['4.2.0', '421', 'systemerror', false]], - '10' => [['4.5.0', '', 'undefined', false]], + '10' => [['5.5.0', '550', 'undefined', false]], '11' => [['4.2.1', '421', 'undefined', false]], '12' => [['5.7.1', '554', 'policyviolation', false]], } From eeaaf4a2cf2545442add58a3ccac623ef3d85fef Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Sun, 5 Mar 2023 15:16:02 +0900 Subject: [PATCH 14/37] Bug fix for checking the value of "deliverystatus" returned from Sisimai::STMP::Status.find --- lib/sisimai/lhost/postfix.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sisimai/lhost/postfix.rb b/lib/sisimai/lhost/postfix.rb index 82b7f838..4badfbf1 100644 --- a/lib/sisimai/lhost/postfix.rb +++ b/lib/sisimai/lhost/postfix.rb @@ -243,7 +243,7 @@ def inquire(mhead, mbody) if e['status'] == '' || e['status'].start_with?('4.0.0', '5.0.0') # Check the value of D.S.N. in anotherset as = Sisimai::SMTP::Status.find(anotherset['diagnosis']) - if as && as[-3, 3] != '0.0' + if as.size > 0 && as[-3, 3] != '0.0' # The D.S.N. is neither an empty nor *.0.0 e['status'] = as end From 2eb742721453a663a7b8056fcf0142220a65272c Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:25:23 +0900 Subject: [PATCH 15/37] Reduce regular expressions --- lib/sisimai/address.rb | 110 +++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 47 deletions(-) diff --git a/lib/sisimai/address.rb b/lib/sisimai/address.rb index 21f3f40f..bd9f611a 100644 --- a/lib/sisimai/address.rb +++ b/lib/sisimai/address.rb @@ -35,6 +35,14 @@ class Address }.freeze Delimiters = { '<' => 1, '>' => 1, '(' => 1, ')' => 1, '"' => 1, ',' => 1 }.freeze + # Return pseudo recipient or sender address + # @param [Symbol] argv0 Address type: true = recipient, false = sender + # @return [String, nil] Pseudo recipient address or sender address or nil when the argv1 is + # neither :r nor :s + def self.undisclosed(argv0 = false) + return sprintf('undisclosed-%s-in-headers@libsisimai.org.invalid', argv0 ? 'recipient' : 'sender') + end + # Check that the argument is an email address or not # @param [String] email Email address string # @return [True,False] true: is an email address @@ -48,34 +56,26 @@ def self.is_emailaddress(email) end # Check that the argument is mailer-daemon or not - # @param [String] email Email address + # @param [String] argv0 Email address # @return [True,False] true: mailer-daemon # false: Not mailer-daemon - def self.is_mailerdaemon(email) - return false unless email.is_a?(::String) - regex = %r/(?: - (?:mailer-daemon|postmaster)[@] - |[<(](?:mailer-daemon|postmaster)[)>] - |\A(?:mailer-daemon|postmaster)\z - |[ ]?mailer-daemon[ ] - ) - /x.freeze - return true if email.downcase =~ regex + def self.is_mailerdaemon(argv0 = nil) + return false unless argv0 + return false unless argv0.size > 0 + return false unless argv0.is_a?(::String) + + email = argv0.downcase + postmaster = [ + 'mailer-daemon@', '', '(mailer-daemon)', ' mailer-daemon ', + 'postmaster@', '', '(postmaster)' + ].freeze + + return true if postmaster.any? { |a| email.include?(a) } + return true if email == 'mailer-daemon' + return true if email == 'postmaster' return false end - # Return pseudo recipient or sender address - # @param [Symbol] argv1 Address type: :r or :s - # @return [String, nil] Pseudo recipient address or sender address or nil when the argv1 is - # neither :r nor :s - def self.undisclosed(argv1) - return nil unless argv1 - return nil unless %w[r s].index(argv1) - - local = argv1 == 'r' ? 'recipient' : 'sender' - return sprintf('undisclosed-%s-in-headers@libsisimai.org.invalid', local) - end - def self.find(argv1 = nil, addrs = false) # Email address parser with a name and a comment # @param [String] argv1 String including email address @@ -252,10 +252,12 @@ def self.find(argv1 = nil, addrs = false) unless v[:address].empty? # Remove the comment from the address - if cv = v[:address].match(/(.*)([(].+[)])(.*)/) + if Sisimai::String.aligned(v[:address], ['(', ')']) # (nyaan)nekochan@example.org, nekochan(nyaan)cat@example.org or nekochan(nyaan)@example.org - v[:address] = cv[1] << cv[3] - v[:comment] = cv[2] + p1 = v[:address].index('(') + p2 = v[:address].index(')') + v[:address] = v[:address][0, p1] + v[:address][p2 + 1, v[:address].size] + v[:comment] = v[:address][p1, p2 - p1 - 1] end readbuffer << v end @@ -264,7 +266,7 @@ def self.find(argv1 = nil, addrs = false) while e = readbuffer.shift do # The element must not include any character except from 0x20 to 0x7e. next if e[:address] =~ /[^\x20-\x7e]/ - unless e[:address] =~ /\A.+[@].+\z/ + if e[:address].include?('@') == false # Allow if the argument is MAILER-DAEMON next unless Sisimai::Address.is_mailerdaemon(e[:address]) end @@ -343,58 +345,72 @@ def self.expand_alias(email) attr_accessor :name, :comment # Constructor of Sisimai::Address - # @param [Hash] argv1 Email address, name, and other elements + # @param [Hash] argvs Email address, name, and other elements # @return [Sisimai::Address] Object or nil when the email address was not valid. # @example new({address: 'neko@example.org', name: 'Neko', comment: '(nyaan)')} # => Sisimai::Address object - def initialize(argv1) - return nil unless argv1.is_a? Hash - return nil unless argv1[:address] - return nil if argv1[:address].empty? + def initialize(argvs) + return nil unless argvs.is_a? Hash + return nil unless argvs[:address] + return nil if argvs[:address].empty? heads = ['<'] tails = ['>', ',', '.', ';'] - if cv = argv1[:address].match(/\A([^\s]+)[@]([^@]+)\z/) || - argv1[:address].match(/\A(["].+?["])[@]([^@]+)\z/) + point = argvs[:address].rindex('@') + if cv = argvs[:address].match(/\A([^\s]+)[@]([^@]+)\z/) || + argvs[:address].match(/\A(["].+?["])[@]([^@]+)\z/) # Get the local part and the domain part from the email address - lpart = cv[1]; heads.each { |e| lpart.gsub!(/\A#{e}/, '') if lpart.start_with?(e) } - dpart = cv[2]; tails.each { |e| dpart.gsub!(/#{e}\z/, '') if dpart.end_with?(e) } - email = Sisimai::Address.expand_verp(argv1[:address]) + lpart = argvs[:address][0, point] + dpart = argvs[:address][point + 1, argvs[:address].size] + email = Sisimai::Address.expand_verp(argvs[:address]) aname = nil unless email # Is not VERP address, try to expand the address as an alias - email = Sisimai::Address.expand_alias(argv1[:address]) || '' + email = Sisimai::Address.expand_alias(argvs[:address]) || '' aname = true unless email.empty? end - if email =~ /\A.+[@].+?\z/ + if email.include?('@') # The address is a VERP or an alias if aname # The address is an alias: neko+nyaan@example.jp - @alias = argv1[:address] + @alias = argvs[:address] else # The address is a VERP: b+neko=example.jp@example.org - @verp = argv1[:address] + @verp = argvs[:address] + end + end + + heads.each do |e| + while lpart[0, 1] == e + lpart[0, 1] = '' end end + + tails.each do |e| + while dpart[-1, 1] == e + dpart[-1, 1] = '' + end + end + @user = lpart @host = dpart @address = lpart + '@' + dpart else # The argument does not include "@" - return nil unless Sisimai::Address.is_mailerdaemon(argv1[:address]) - return nil if argv1[:address].include?(' ') + return nil unless Sisimai::Address.is_mailerdaemon(argvs[:address]) + return nil if argvs[:address].include?(' ') # The argument does not include " " - @user = argv1[:address] + @user = argvs[:address] @host ||= '' - @address = argv1[:address] + @address = argvs[:address] end @alias ||= '' @verp ||= '' - @name = argv1[:name] || '' - @comment = argv1[:comment] || '' + @name = argvs[:name] || '' + @comment = argvs[:comment] || '' end # Check whether the object has valid content or not From c9e90ab3c29a755b8a5e771fad50826d35b85f05 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:28:11 +0900 Subject: [PATCH 16/37] Reduce regular expressions at each file in lib/sisimai/lhost --- lib/sisimai/lhost/activehunter.rb | 8 +- lib/sisimai/lhost/amavis.rb | 1 - lib/sisimai/lhost/amazonses.rb | 4 +- lib/sisimai/lhost/amazonworkmail.rb | 12 ++- lib/sisimai/lhost/aol.rb | 1 - lib/sisimai/lhost/apachejames.rb | 18 ++--- lib/sisimai/lhost/barracuda.rb | 1 - lib/sisimai/lhost/bigfoot.rb | 12 +-- lib/sisimai/lhost/biglobe.rb | 3 +- lib/sisimai/lhost/courier.rb | 5 +- lib/sisimai/lhost/domino.rb | 10 +-- lib/sisimai/lhost/einsundeins.rb | 6 +- lib/sisimai/lhost/exchange2003.rb | 26 ++++--- lib/sisimai/lhost/exchange2007.rb | 72 ++++++++++-------- lib/sisimai/lhost/exim.rb | 54 +++++++------- lib/sisimai/lhost/ezweb.rb | 52 ++++--------- lib/sisimai/lhost/facebook.rb | 5 +- lib/sisimai/lhost/fml.rb | 70 ++++++++--------- lib/sisimai/lhost/gmail.rb | 14 ++-- lib/sisimai/lhost/gmx.rb | 8 +- lib/sisimai/lhost/gsuite.rb | 20 ++--- lib/sisimai/lhost/imailserver.rb | 19 +++-- lib/sisimai/lhost/interscanmss.rb | 27 ++++--- lib/sisimai/lhost/kddi.rb | 20 ++--- lib/sisimai/lhost/mailfoundry.rb | 4 +- lib/sisimai/lhost/mailmarshalsmtp.rb | 26 ++++--- lib/sisimai/lhost/mailru.rb | 28 +++---- lib/sisimai/lhost/mcafee.rb | 33 +++----- lib/sisimai/lhost/messagelabs.rb | 15 ++-- lib/sisimai/lhost/messagingserver.rb | 57 +++++++------- lib/sisimai/lhost/mfilter.rb | 11 +-- lib/sisimai/lhost/mxlogic.rb | 27 ++++--- lib/sisimai/lhost/notes.rb | 15 ++-- lib/sisimai/lhost/opensmtpd.rb | 6 +- lib/sisimai/lhost/outlook.rb | 5 +- lib/sisimai/lhost/postfix.rb | 108 +++++++++++++++------------ lib/sisimai/lhost/powermta.rb | 5 +- lib/sisimai/lhost/qmail.rb | 59 +++++++-------- lib/sisimai/lhost/receivingses.rb | 5 +- lib/sisimai/lhost/sendgrid.rb | 31 ++++---- lib/sisimai/lhost/sendmail.rb | 77 +++++++++++-------- lib/sisimai/lhost/surfcontrol.rb | 20 ++--- lib/sisimai/lhost/v5sendmail.rb | 51 +++++++------ lib/sisimai/lhost/verizon.rb | 39 +++++----- lib/sisimai/lhost/x1.rb | 16 ++-- lib/sisimai/lhost/x2.rb | 12 ++- lib/sisimai/lhost/x3.rb | 12 +-- lib/sisimai/lhost/x4.rb | 95 +++++++++++++---------- lib/sisimai/lhost/x5.rb | 13 ++-- lib/sisimai/lhost/x6.rb | 34 +++++++-- lib/sisimai/lhost/yahoo.rb | 7 +- lib/sisimai/lhost/yandex.rb | 12 +-- lib/sisimai/lhost/zoho.rb | 12 +-- 53 files changed, 677 insertions(+), 626 deletions(-) diff --git a/lib/sisimai/lhost/activehunter.rb b/lib/sisimai/lhost/activehunter.rb index 22242789..86f2e0f1 100644 --- a/lib/sisimai/lhost/activehunter.rb +++ b/lib/sisimai/lhost/activehunter.rb @@ -45,27 +45,27 @@ def inquire(mhead, mbody) # 550 sorry, no mailbox here by that name (#5.1.1 - chkusr) v = dscontents[-1] - if cv = e.match(/\A[>]{3}[ ]+.+[<]([^ ]+?[@][^ ]+?)[>]\z/) + if e.start_with?('>>> ') && e.index('@') > 1 # >>> kijitora@example.org if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = Sisimai::Address.s3s4(e[e.index('<'), e.size]) v['diagnosis'] = '' recipients += 1 else # ----- Transcript of session follows ----- # 550 sorry, no mailbox here by that name (#5.1.1 - chkusr) - next unless e =~ /\A[0-9A-Za-z]+/ + next if e[0, 1].ord < 48 + next if e[0, 1].ord > 122 next unless v['diagnosis'].empty? v['diagnosis'] = e end end return nil unless recipients > 0 - require 'sisimai/string' dscontents.each { |e| e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) } return { 'ds' => dscontents, 'rfc822' => emailparts[1] } end diff --git a/lib/sisimai/lhost/amavis.rb b/lib/sisimai/lhost/amavis.rb index cd4a1c83..3a2abf5f 100644 --- a/lib/sisimai/lhost/amavis.rb +++ b/lib/sisimai/lhost/amavis.rb @@ -79,7 +79,6 @@ def inquire(mhead, mbody) # Subject: Undeliverable mail, MTA-BLOCKED return nil unless mhead['from'].to_s.start_with?('"Content-filter at ') - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field diff --git a/lib/sisimai/lhost/amazonses.rb b/lib/sisimai/lhost/amazonses.rb index 1bffa263..cb488df6 100644 --- a/lib/sisimai/lhost/amazonses.rb +++ b/lib/sisimai/lhost/amazonses.rb @@ -178,7 +178,6 @@ def inquire(mhead, mbody) v['lhost'] = o['reportingMTA'] || '' v['diagnosis'] = o['smtpResponse'] || '' v['status'] = Sisimai::SMTP::Status.find(v['diagnosis']) || '' - v['replycode'] = Sisimai::SMTP::Reply.find(v['diagnosis']) || '' v['reason'] = 'delivered' v['action'] = 'delivered' @@ -310,7 +309,7 @@ def inquire(mhead, mbody) permessage.each_key { |a| e[a] ||= permessage[a] || '' } e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].to_s.tr("\n", ' ')) - if e['status'].to_s.start_with?('5.0.0', '5.1.0', '4.0.0', '4.1.0') + if e['status'].to_s.end_with?('.0.0', '.1.0') # Get other D.S.N. value from the error message errormessage = e['diagnosis'] @@ -320,6 +319,7 @@ def inquire(mhead, mbody) end e['status'] = Sisimai::SMTP::Status.find(errormessage) || e['status'] end + e['replycode'] ||= Sisimai::SMTP::Reply.find(e['diagnosis'], e['status']) MessagesOf.each_key do |r| # Verify each regular expression of session errors diff --git a/lib/sisimai/lhost/amazonworkmail.rb b/lib/sisimai/lhost/amazonworkmail.rb index 8d0982a6..c8c3a258 100644 --- a/lib/sisimai/lhost/amazonworkmail.rb +++ b/lib/sisimai/lhost/amazonworkmail.rb @@ -102,7 +102,7 @@ def inquire(mhead, mbody) permessage.each_key { |a| e[a] ||= permessage[a] || '' } e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) - if e['status'].to_s.start_with?('5.0.0', '5.1.0', '4.0.0', '4.1.0') + if e['status'].to_s.end_with?('.0.0', '.1.0') # Get other D.S.N. value from the error message errormessage = e['diagnosis'] @@ -113,12 +113,10 @@ def inquire(mhead, mbody) e['status'] = Sisimai::SMTP::Status.find(errormessage) || e['status'] end - if cv = e['diagnosis'].match(/[<]([245]\d\d)[ ].+[>]/) - # 554 4.4.7 Message expired: unable to deliver in 840 minutes. - # <421 4.4.2 Connection timed out> - e['replycode'] = cv[1] - end - e['reason'] ||= Sisimai::SMTP::Status.name(e['status']) || '' + # 554 4.4.7 Message expired: unable to deliver in 840 minutes. + # <421 4.4.2 Connection timed out> + e['replycode'] = Sisimai::SMTP::Reply.find(e['diagnosis']) || '' + e['reason'] ||= Sisimai::SMTP::Status.name(e['status']) || '' end return { 'ds' => dscontents, 'rfc822' => emailparts[1] } diff --git a/lib/sisimai/lhost/aol.rb b/lib/sisimai/lhost/aol.rb index 12bd499a..bb0b7752 100644 --- a/lib/sisimai/lhost/aol.rb +++ b/lib/sisimai/lhost/aol.rb @@ -31,7 +31,6 @@ def inquire(mhead, mbody) # X-Outbound-Mail-Relay-Sender: rfc822; shironeko@aol.example.jp return nil unless mhead['x-aol-ip'] - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field diff --git a/lib/sisimai/lhost/apachejames.rb b/lib/sisimai/lhost/apachejames.rb index 0cd3fb71..fdad9804 100644 --- a/lib/sisimai/lhost/apachejames.rb +++ b/lib/sisimai/lhost/apachejames.rb @@ -32,7 +32,7 @@ def inquire(mhead, mbody) bodyslices = emailparts[0].split("\n") readcursor = 0 # (Integer) Points the current cursor position recipients = 0 # (Integer) The number of 'Final-Recipient' header - diagnostic = '' # (String) Alternative diagnostic message + issuedcode = '' # (String) Alternative diagnostic message subjecttxt = nil # (String) Alternative Subject text gotmessage = nil # (Boolean) Flag for error message v = nil @@ -60,23 +60,23 @@ def inquire(mhead, mbody) # Number of lines: 64 v = dscontents[-1] - if cv = e.match(/\A[ ][ ]RCPT[ ]TO:[ ]([^ ]+[@][^ ]+)\z/) + if e.start_with?(' RCPT TO: ') # RCPT TO: kijitora@example.org if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = e[12, e.size] recipients += 1 - elsif cv = e.match(/\A[ ][ ]Sent[ ]date:[ ](.+)\z/) + elsif e.start_with?(' Sent date: ') # Sent date: Thu Apr 29 01:20:50 JST 2015 - v['date'] = cv[1] + v['date'] = e[13, e.size] - elsif cv = e.match(/\A[ ][ ]Subject:[ ](.+)\z/) + elsif e.start_with?(' Subject: ') # Subject: Nyaaan - subjecttxt = cv[1] + subjecttxt = e[11, e.size] else next if gotmessage if v['diagnosis'] @@ -106,9 +106,9 @@ def inquire(mhead, mbody) return nil unless recipients > 0 # Set the value of subjecttxt as a Subject if there is no original message in the bounce mail. - emailparts[1] << ('Subject: ' << subjecttxt << "\n") unless emailparts[1] =~ /^Subject: / + emailparts[1] << ('Subject: ' << subjecttxt << "\n") unless emailparts[1].index("\nSubject:") - dscontents.each { |e| e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'] || diagnostic) } + dscontents.each { |e| e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'] || issuedcode) } return { 'ds' => dscontents, 'rfc822' => emailparts[1] } end def description; return 'Java Apache Mail Enterprise Server'; end diff --git a/lib/sisimai/lhost/barracuda.rb b/lib/sisimai/lhost/barracuda.rb index e720352c..49956c2e 100644 --- a/lib/sisimai/lhost/barracuda.rb +++ b/lib/sisimai/lhost/barracuda.rb @@ -19,7 +19,6 @@ def inquire(mhead, mbody) # Subject: **Message you sent blocked by our bulk email filter** return nil unless mhead['subject'].to_s.end_with?('our bulk email filter**') - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field diff --git a/lib/sisimai/lhost/bigfoot.rb b/lib/sisimai/lhost/bigfoot.rb index a29c282d..c1e5acfa 100644 --- a/lib/sisimai/lhost/bigfoot.rb +++ b/lib/sisimai/lhost/bigfoot.rb @@ -7,7 +7,7 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['Content-Type: message/partial'].freeze - MarkingsOf = { message: %r/\A[ ]+[-]+[ ]*Transcript of session follows/ }.freeze + MarkingsOf = { message: ' ----- Transcript of session follows -----' }.freeze # Parse bounce messages from Bigfoot # @param [Hash] mhead Message headers of a bounce email @@ -43,7 +43,7 @@ def inquire(mhead, mbody) if readcursor == 0 # Beginning of the bounce message or message/delivery-status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + readcursor |= Indicators[:deliverystatus] if e.start_with?(MarkingsOf[:message]) next end next if (readcursor & Indicators[:deliverystatus]) == 0 @@ -91,15 +91,15 @@ def inquire(mhead, mbody) if e.start_with?('>>> ') # >>> DATA thecommand = Sisimai::SMTP::Command.find(e) - elsif cv = e.match(/\A[<]{3}[ ]+(.+)\z/) + elsif e.start_with?('<<< ') # <<< Response - esmtpreply = cv[1] + esmtpreply = e[4, e.size - 4] end else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << Sisimai::String.sweep(e[1, e.size]) readslices[-1] = 'Diagnostic-Code: ' << e end end diff --git a/lib/sisimai/lhost/biglobe.rb b/lib/sisimai/lhost/biglobe.rb index bfc83c7c..0ab08c09 100644 --- a/lib/sisimai/lhost/biglobe.rb +++ b/lib/sisimai/lhost/biglobe.rb @@ -22,7 +22,8 @@ class << self # @return [Hash] Bounce data list and message/rfc822 part # @return [Nil] it failed to parse or the arguments are missing def inquire(mhead, mbody) - return nil unless mhead['from'] =~ /postmaster[@](?:biglobe|inacatv|tmtv|ttv)[.]ne[.]jp/ + return nil unless mhead['from'].include?('postmaster@') + return nil unless %w[biglobe inacatv tmtv ttv].any? { |a| mhead['from'].include?('@' + a + '.ne.jp') } return nil unless mhead['subject'].start_with?('Returned mail:') dscontents = [Sisimai::Lhost.DELIVERYSTATUS] diff --git a/lib/sisimai/lhost/courier.rb b/lib/sisimai/lhost/courier.rb index c910ce77..c3d1abc5 100644 --- a/lib/sisimai/lhost/courier.rb +++ b/lib/sisimai/lhost/courier.rb @@ -31,10 +31,11 @@ class << self def inquire(mhead, mbody) match = 0 match += 1 if mhead['from'].include?('Courier mail server at ') - match += 1 if mhead['subject'] =~ /(?:NOTICE: mail delivery status[.]|WARNING: delayed mail[.])/ + match += 1 if mhead['subject'].include?('NOTICE: mail delivery status.') + match += 1 if mhead['subject'].include?('WARNING: delayed mail.') if mhead['message-id'] # Message-ID: - match += 1 if mhead['message-id'] =~ /\A[<]courier[.][0-9A-F]+[.]/ + match += 1 if mhead['message-id'].start_with?(' 0 diff --git a/lib/sisimai/lhost/domino.rb b/lib/sisimai/lhost/domino.rb index 5d2049e7..19e1cbd3 100644 --- a/lib/sisimai/lhost/domino.rb +++ b/lib/sisimai/lhost/domino.rb @@ -76,10 +76,10 @@ def inquire(mhead, mbody) v['recipient'] ||= e recipients += 1 - elsif cv = e.match(/\A[ ][ ]([^ ]+[@][^ ]+)\z/) + elsif e.start_with?(' ') && e.include?('@') && e.index(' ', 3).nil? # Continued from the line "was not delivered to:" # kijitora@example.net - v['recipient'] = Sisimai::Address.s3s4(cv[1]) + v['recipient'] = Sisimai::Address.s3s4(e[2, e.size]) elsif e.start_with?('because:') # because: @@ -89,9 +89,9 @@ def inquire(mhead, mbody) # Error message, continued from the line "because:" v['diagnosis'] = e - elsif cv = e.match(/\A[ ][ ]Subject: (.+)\z/) + elsif e.start_with?(' Subject: ') # Subject: Nyaa - subjecttxt = cv[1] + subjecttxt = e[11, e.size] elsif f = Sisimai::RFC1894.match(e) # There are some fields defined in RFC3464, try to match @@ -136,7 +136,7 @@ def inquire(mhead, mbody) end # Set the value of subjecttxt as a Subject if there is no original message in the bounce mail. - emailparts[1] << ('Subject: ' << subjecttxt << "\n") unless emailparts[1] =~ /^Subject: / + emailparts[1] << ('Subject: ' << subjecttxt << "\n") unless emailparts[1].include?("\nSubject:") return { 'ds' => dscontents, 'rfc822' => emailparts[1] } end diff --git a/lib/sisimai/lhost/einsundeins.rb b/lib/sisimai/lhost/einsundeins.rb index 4ee0fca0..e8219ea3 100644 --- a/lib/sisimai/lhost/einsundeins.rb +++ b/lib/sisimai/lhost/einsundeins.rb @@ -51,14 +51,14 @@ def inquire(mhead, mbody) # http://postmaster.1and1.com/en/error-messages?ip=%1s v = dscontents[-1] - if cv = e.match(/\A([^ ]+[@][^ ]+?)[:]?\z/) + if e.include?('@') && e.include?(' ') == false # general@example.eu if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = Sisimai::Address.s3s4(e) recipients += 1 elsif e.start_with?(StartingOf[:error][0]) @@ -93,7 +93,7 @@ def inquire(mhead, mbody) e['status'] = Sisimai::SMTP::Status.find(e['diagnosis']) else # For the following reason: - e['diagnosis'].gsub(/\A#{StartingOf[:error][0]}/, '') + e['diagnosis'][0, StartingOf[:error][0].size] = '' end e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) diff --git a/lib/sisimai/lhost/exchange2003.rb b/lib/sisimai/lhost/exchange2003.rb index e943f2d9..5010edf2 100644 --- a/lib/sisimai/lhost/exchange2003.rb +++ b/lib/sisimai/lhost/exchange2003.rb @@ -135,10 +135,10 @@ def inquire(mhead, mbody) v['msexch'] = false recipients += 1 - elsif cv = e.match(/\A[ ]+(MSEXCH:.+)\z/) + elsif e.start_with?(' ') && e.include?('MSEXCH:') # MSEXCH:IMS:KIJITORA CAT:EXAMPLE:EXCHANGE 0 (000C05A6) Unknown Recipient v['diagnosis'] ||= '' - v['diagnosis'] << cv[1] + v['diagnosis'] << e[e.index('MSEXCH:'), e.size] else next if v['msexch'] if v['diagnosis'].to_s.start_with?('MSEXCH:') @@ -159,24 +159,23 @@ def inquire(mhead, mbody) # Subject: ... # Sent: Thu, 29 Apr 2010 18:14:35 +0000 # - if cv = e.match(/\A[ ]+To:[ ]+(.+)\z/) + if e.start_with?(' To: ') || e.start_with?(' To: ') # To: shironeko@example.jp next unless connheader['to'].empty? - connheader['to'] = cv[1] + connheader['to'] = e[e.rindex(' ') + 1, e.size] connvalues += 1 - elsif cv = e.match(/\A[ ]+Subject:[ ]+(.+)\z/) + elsif e.start_with?(' Subject: ') || e.start_with?(' Subject: ') # Subject: ... next unless connheader['subject'].empty? - connheader['subject'] = cv[1] + connheader['subject'] = e[e.rindex(' ') + 1, e.size] connvalues += 1 - elsif cv = e.match(%r|\A[ ]+Sent:[ ]+([A-Z][a-z]{2},.+[-+]\d{4})\z|) || - e.match(%r|\A[ ]+Sent:[ ]+(\d+[/]\d+[/]\d+[ ]+\d+:\d+:\d+[ ].+)|) + elsif e.start_with?(' Sent: ') || e.start_with?(' Sent: ') # Sent: Thu, 29 Apr 2010 18:14:35 +0000 # Sent: 4/29/99 9:19:59 AM next unless connheader['date'].empty? - connheader['date'] = cv[1] + connheader['date'] = e[e.index(':') + 2, e.size] connvalues += 1 end end @@ -185,10 +184,13 @@ def inquire(mhead, mbody) dscontents.each do |e| e.delete('msexch') - if cv = e['diagnosis'].match(/\AMSEXCH:.+[ ]*[(]([0-9A-F]{8})[)][ ]*(.*)\z/) + e['diagnosis'] ||= '' + if e['diagnosis'].start_with?('MSEXCH:') # MSEXCH:IMS:KIJITORA CAT:EXAMPLE:EXCHANGE 0 (000C05A6) Unknown Recipient - capturedcode = cv[1] - errormessage = cv[2] + p1 = e['diagnosis'].index('(') || -1 + p2 = e['diagnosis'].index(')') || -1 + capturedcode = e['diagnosis'][p1 + 1, 8] + errormessage = e['diagnosis'][p2 + 1, e['diagnosis'].size] ErrorCodes.each_key do |r| # Find captured code from the error code table diff --git a/lib/sisimai/lhost/exchange2007.rb b/lib/sisimai/lhost/exchange2007.rb index 7453ba2d..f186b691 100644 --- a/lib/sisimai/lhost/exchange2007.rb +++ b/lib/sisimai/lhost/exchange2007.rb @@ -12,20 +12,17 @@ class << self 'Intestazioni originali del messaggio:', # it-CH ].freeze MarkingsOf = { - message: %r{\A(?: - Diagnostic[ ]information[ ]for[ ]administrators: # en-US - |Informations[ ]de[ ]diagnostic[ ]pour[ ]les[ ]administrateurs # fr-FR - |Informazioni[ ]di[ ]diagnostica[ ]per[ ]gli[ ]amministratori # it-CH - ) - }x, - error: %r/ ((?:RESOLVER|QUEUE)[.][A-Za-z]+(?:[.]\w+)?);/, - rhost: %r{\A(?: - Generating[ ]server # en-US - |Serveur[ ]de[ ]g[^ ]+ration[ ] # fr-FR/Serveur de g辿n辿ration - |Server[ ]di[ ]generazione # it-CH - ):[ ]?(.*) - }x, - subject: %r/\A(?:Undeliverable|Non_remis_|Non[ ]recapitabile):/, + message: [ + 'Diagnostic information for administrators:', # en-US + 'Informations de diagnostic pour les administrateurs', # fr-FR + 'Informazioni di diagnostica per gli amministratori', # it-CH + ], + error: [' RESOLVER.', ' QUEUE.'], + rhost: [ + 'Generating server', # en-US + 'Serveur de g', # fr-FR/Serveur de g�Hn�Hration + 'Server di generazione', # it-CH + ] }.freeze NDRSubject = { 'SMTPSEND.DNS.NonExistentDomain' => 'hostunknown', # 554 5.4.4 SMTPSEND.DNS.NonExistentDomain @@ -49,9 +46,16 @@ class << self # @return [Nil] it failed to parse or the arguments are missing def inquire(mhead, mbody) # Content-Language: en-US, fr-FR - return nil unless mhead['subject'] =~ MarkingsOf[:subject] + match = nil + match ||= 1 if mhead['subject'].start_with?('Undeliverable') + match ||= 1 if mhead['subject'].start_with?('Non_remis_') + match ||= 1 if mhead['subject'].start_with?('Non recapitabile') + return nil unless match return nil unless mhead['content-language'] - return nil unless mhead['content-language'] =~ /\A[a-z]{2}(?:[-][A-Z]{2})?\z/ + + match += 1 if mhead['content-language'].size == 2 # JP + match += 1 if mhead['content-language'].size == 5 # ja-JP + return nil unless match > 1 # These headers exist only a bounce mail from Office365 return nil if mhead['x-ms-exchange-crosstenant-originalarrivaltime'] @@ -73,7 +77,7 @@ def inquire(mhead, mbody) # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + readcursor |= Indicators[:deliverystatus] if MarkingsOf[:message].any? { |a| e.start_with?(a) } next end next if (readcursor & Indicators[:deliverystatus]) == 0 @@ -89,14 +93,14 @@ def inquire(mhead, mbody) # Original message headers: v = dscontents[-1] - if cv = e.match(/\A([^ @]+[@][^ @]+)\z/) + if e.include?('@') && e.include?(' ') == false # kijitora@example.jp if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = Sisimai::Address.s3s4(e) v['diagnosis'] = '' recipients += 1 @@ -119,26 +123,32 @@ def inquire(mhead, mbody) # Diagnostic information for administrators: # # Generating server: mta22.neko.example.org - next unless cv = e.match(MarkingsOf[:rhost]) + next unless MarkingsOf[:rhost].any? { |a| e.start_with?(a) } next unless connheader['rhost'].empty? - connheader['rhost'] = cv[1] + connheader['rhost'] = e[e.index(':') + 1, e.size] connvalues += 1 end end return nil unless recipients > 0 dscontents.each do |e| - if cv = e['diagnosis'].match(MarkingsOf[:error]) - # #550 5.1.1 RESOLVER.ADR.RecipNotFound; not found ## - f = cv[1] - NDRSubject.each_key do |r| - # Try to match with error subject strings - next unless f == r - e['reason'] = NDRSubject[r] - break - end - end + p = -1 e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) + MarkingsOf[:error].each do |q| + # Find an error message, get an error code. + p = e['diagnosis'].index(q) || -1 + break if p > -1 + end + next unless p > 0 + + # #550 5.1.1 RESOLVER.ADR.RecipNotFound; not found ## + f = e['diagnosis'][p + 1, e['diagnosis'].index(';') - p -1] + NDRSubject.each_key do |r| + # Try to match with error subject strings + next unless f == r + e['reason'] = NDRSubject[r] + break + end end return { 'ds' => dscontents, 'rfc822' => emailparts[1] } diff --git a/lib/sisimai/lhost/exim.rb b/lib/sisimai/lhost/exim.rb index 54fe09da..1fb56f06 100644 --- a/lib/sisimai/lhost/exim.rb +++ b/lib/sisimai/lhost/exim.rb @@ -14,9 +14,8 @@ class << self '------ This is a copy of the message, including all the headers. ------', 'Content-Type: message/rfc822', ].freeze - StartingOf = { deliverystatus: ['Content-Type: message/delivery-status'] }.freeze - MarkingsOf = { - # Error text regular expressions which defined in exim/src/deliver.c + StartingOf = { + # Error text strings which defined in exim/src/deliver.c # # deliver.c:6292| fprintf(f, # deliver.c:6293|"This message was created automatically by mail delivery software.\n"); @@ -33,19 +32,19 @@ class << self # deliver.c:6304|"could not be delivered to one or more of its recipients. The following\n" # deliver.c:6305|"address(es) failed:\n", sender_address); # deliver.c:6306| } - alias: %r/\A([ ]+an undisclosed address)\z/, - frozen: %r/\AMessage [^ ]+ (?:has been frozen|was frozen on arrival)/, - message: %r{\A(?> - This[ ]message[ ]was[ ]created[ ]automatically[ ]by[ ]mail[ ]delivery[ ]software[.] - |A[ ]message[ ]that[ ]you[ ]sent[ ]was[ ]rejected[ ]by[ ]the[ ]local[ ]scanning[ ]code - |A[ ]message[ ]that[ ]you[ ]sent[ ]contained[ ]one[ ]or[ ]more[ ]recipient[ ]addresses[ ] - |A[ ]message[ ]that[ ]you[ ]sent[ ]could[ ]not[ ]be[ ]delivered[ ]to[ ]all[ ]of[ ]its[ ]recipients - |Message[ ][^ ]+[ ](?:has[ ]been[ ]frozen|was[ ]frozen[ ]on[ ]arrival) - |The[ ][^ ]+[ ]router[ ]encountered[ ]the[ ]following[ ]error[(]s[)]: - ) - }x, + deliverystatus: ['Content-Type: message/delivery-status'], + frozen: [' has been frozen', ' was frozen on arrival'], + message: [ + 'This message was created automatically by mail delivery software.', + 'A message that you sent was rejected by the local scannning code', + 'A message that you sent contained one or more recipient addresses ', + 'A message that you sent could not be delivered to all of its recipients', + ' has been frozen', + ' was frozen on arrival', + ' router encountered the following error(s):', + ], }.freeze - + MarkingsOf = { alias: %r/\A([ ]+an undisclosed address)\z/ }.freeze ReCommands = [ # transports/smtp.c:564| *message = US string_sprintf("SMTP error from remote mail server after %s%s: " # transports/smtp.c:837| string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: " @@ -124,25 +123,22 @@ class << self # @return [Hash] Bounce data list and message/rfc822 part # @return [Nil] it failed to parse or the arguments are missing def inquire(mhead, mbody) - return nil if mhead['from'] =~ /[@].+[.]mail[.]ru[>]?/ + return nil if mhead['from'].include?('.mail.ru') # Message-Id: # X-Failed-Recipients: kijitora@example.ed.jp match = 0 match += 1 if mhead['from'].start_with?('Mail Delivery System') match += 1 if mhead['message-id'].to_s =~ %r/\A[<]\w{7}[-]\w{6}[-]\w{2}[@]/ - match += 1 if mhead['subject'] =~ %r{(?: - Mail[ ]delivery[ ]failed(:[ ]returning[ ]message[ ]to[ ]sender)? - |Warning:[ ]message[ ][^ ]+[ ]delayed[ ]+ - |Delivery[ ]Status[ ]Notification - |Mail[ ]failure - |Message[ ]frozen - |error[(]s[)][ ]in[ ]forwarding[ ]or[ ]filtering - ) - }x + match += 1 if %w[ + 'Delivery Status Notification', + 'Mail delivery failed', + 'Mail failure', + 'Message frozen', + 'Warning: message ', + 'error(s) in forwarding or filtering'].any? { |a| mhead['subject'].include?(a) } return nil if match < 2 - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) @@ -164,9 +160,9 @@ def inquire(mhead, mbody) # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or message/delivery-status part - if e =~ MarkingsOf[:message] + if StartingOf[:message].any? { |a| e.include?(a) } readcursor |= Indicators[:deliverystatus] - next unless e =~ MarkingsOf[:frozen] + next unless StartingOf[:frozen].any? { |a| e.include?(a) } end end next if (readcursor & Indicators[:deliverystatus]) == 0 @@ -225,7 +221,7 @@ def inquire(mhead, mbody) else next if e.empty? - if e =~ MarkingsOf[:frozen] + if StartingOf[:frozen].any? { |a| e.include?(a) } # Message *** has been frozen by the system filter. # Message *** was frozen on arrival by ACL. v['alterrors'] ||= '' diff --git a/lib/sisimai/lhost/ezweb.rb b/lib/sisimai/lhost/ezweb.rb index f9814d2e..f68b40a3 100644 --- a/lib/sisimai/lhost/ezweb.rb +++ b/lib/sisimai/lhost/ezweb.rb @@ -7,34 +7,22 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['--------------------------------------------------', 'Content-Type: message/rfc822'].freeze - MarkingsOf = { - message: %r{\A(?: - The[ ]user[(]s[)][ ] - |Your[ ]message[ ] - |Each[ ]of[ ]the[ ]following - |[<][^ ]+[@][^ ]+[>]\z - ) - }x, - }.freeze + MarkingsOf = { message: ['The user(s) ', 'Your message ', 'Each of the following', '<'] }.freeze ReFailures = { - # notaccept: [ %r/The following recipients did not receive this message:/ ], - 'mailboxfull' => [ - %r/The user[(]s[)] account is temporarily over quota/, - ], + # notaccept: ['The following recipients did not receive this message:'], + 'mailboxfull' => ['The user(s) account is temporarily over quota'], 'suspend' => [ # http://www.naruhodo-au.kddi.com/qa3429203.html # The recipient may be unpaid user...? - %r/The user[(]s[)] account is disabled[.]/, - %r/The user[(]s[)] account is temporarily limited[.]/, + 'The user(s) account is disabled.', + 'The user(s) account is temporarily limited.', ], 'expired' => [ # Your message was not delivered within 0 days and 1 hours. # Remote host is not responding. - %r/Your message was not delivered within /, - ], - 'onhold' => [ - %r/Each of the following recipients was rejected by a remote mail server/, + 'Your message was not delivered within ', ], + 'onhold' => ['Each of the following recipients was rejected by a remote mail server'], }.freeze # Parse bounce messages from au EZweb @@ -54,31 +42,21 @@ def inquire(mhead, mbody) end return nil if match < 2 - require 'sisimai/smtp/command' - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) bodyslices = emailparts[0].split("\n") readcursor = 0 # (Integer) Points the current cursor position recipients = 0 # (Integer) The number of 'Final-Recipient' header - rxboundary = %r/\A__SISIMAI_PSEUDO_BOUNDARY__\z/ + rxmessages = []; ReFailures.each_value { |a| rxmessages << a } v = nil - if mhead['content-type'] - # Get the boundary string and set regular expression for matching with the boundary string. - b0 = Sisimai::RFC2045.boundary(mhead['content-type'], 1) - rxboundary = Regexp.new('\A' << Regexp.escape(b0) << '\z') unless b0.empty? - end - rxmessages = [] - ReFailures.each_value { |a| rxmessages << a } - while e = bodyslices.shift do # Read error messages and delivery status lines from the head of the email to the previous # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + readcursor |= Indicators[:deliverystatus] if MarkingsOf[:message].any? { |a| e.include?(a) } end next if (readcursor & Indicators[:deliverystatus]) == 0 next if e.empty? @@ -121,10 +99,10 @@ def inquire(mhead, mbody) next if Sisimai::String.is_8bit(e) if e.include?('>>> ') # >>> RCPT TO:<******@ezweb.ne.jp> - v['command'] = Sisimai::SMTP::Command.find(e) + v['command'] = Sisimai::SMTP::Command.find(e) || '' else # Check error message - if rxmessages.any? { |messages| messages.any? { |message| e =~ message } } + if rxmessages.any? { |messages| messages.any? { |message| e.include?(message) } } # Check with regular expressions of each error v['diagnosis'] ||= '' v['diagnosis'] << ' ' << e @@ -148,7 +126,7 @@ def inquire(mhead, mbody) end e.delete('alterrors') end - e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) + e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || '' if mhead['x-spasign'].to_s == 'NG' # Content-Type: text/plain; ..., X-SPASIGN: NG (spamghetti, au by EZweb) @@ -162,10 +140,10 @@ def inquire(mhead, mbody) # SMTP command is not RCPT catch :SESSION do ReFailures.each_key do |r| - # Verify each regular expression of session errors + # Try to match with each session error message ReFailures[r].each do |rr| - # Check each regular expression - next unless e['diagnosis'] =~ rr + # Check each error message pattern + next unless e['diagnosis'].include?(rr) e['reason'] = r throw :SESSION end diff --git a/lib/sisimai/lhost/facebook.rb b/lib/sisimai/lhost/facebook.rb index e72eef19..760c9414 100644 --- a/lib/sisimai/lhost/facebook.rb +++ b/lib/sisimai/lhost/facebook.rb @@ -77,7 +77,6 @@ def inquire(mhead, mbody) return nil unless mhead['from'] == 'Facebook ' return nil unless mhead['subject'] == 'Sorry, your message could not be delivered' - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field @@ -139,8 +138,8 @@ def inquire(mhead, mbody) else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << e[e.rindex(' ') + 1, e.size] readslices[-1] = 'Diagnostic-Code: ' << e end end diff --git a/lib/sisimai/lhost/fml.rb b/lib/sisimai/lhost/fml.rb index 722fe475..1caab29e 100644 --- a/lib/sisimai/lhost/fml.rb +++ b/lib/sisimai/lhost/fml.rb @@ -8,40 +8,34 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['Original mail as follows:'].freeze ErrorTitle = { - 'rejected' => %r{(?> - (?:Ignored[ ])*NOT[ ]MEMBER[ ]article[ ]from[ ] - |reject[ ]mail[ ](?:.+:|from)[ ], - |Spam[ ]mail[ ]from[ ]a[ ]spammer[ ]is[ ]rejected - |You[ ].+[ ]are[ ]not[ ]member - ) - }x, - 'systemerror' => %r{(?: - fml[ ]system[ ]error[ ]message - |Loop[ ]Alert:[ ] - |Loop[ ]Back[ ]Warning:[ ] - |WARNING:[ ]UNIX[ ]FROM[ ]Loop - ) - }x, - 'securityerror' => %r/Security Alert/, + 'rejected' => [ + ' are not member', + 'NOT MEMBER article from ', + 'reject mail ', + 'Spam mail from a spammer is rejected', + ], + 'systemerror' => [ + 'fml system error message', + 'Loop Alert: ', + 'Loop Back Warning: ', + 'WARNING: UNIX FROM Loop', + ], + 'securityerror' => ['Security Alert'], }.freeze ErrorTable = { - 'rejected' => %r{(?> - (?:Ignored[ ])*NOT[ ]MEMBER[ ]article[ ]from[ ] - |reject[ ](?: - mail[ ]from[ ].+[@].+ - |since[ ].+[ ]header[ ]may[ ]cause[ ]mail[ ]loop - |spammers: - ) - |You[ ]are[ ]not[ ]a[ ]member[ ]of[ ]this[ ]mailing[ ]list - ) - }x, - 'systemerror' => %r{(?: - Duplicated[ ]Message-ID - |fml[ ].+[ ]has[ ]detected[ ]a[ ]loop[ ]condition[ ]so[ ]that - |Loop[ ]Back[ ]Warning: - ) - }x, - 'securityerror' => %r/Security alert:/, + 'rejected' => [ + ' header may cause mail loop', + 'NOT MEMBER article from ', + 'reject mail from ', + 'reject spammers:', + 'You are not a member of this mailing list', + ], + 'systemerror' => [ + ' has detected a loop condition so that', + 'Duplicated Message-ID', + 'Loop Back Warning:', + ], + 'securityerror' => ['Security alert:'], }.freeze # Parse bounce messages from fml mailling list server/manager @@ -53,7 +47,7 @@ class << self def inquire(mhead, mbody) return nil unless mhead['x-mlserver'] return nil unless mhead['from'].include?('-admin@') - return nil unless mhead['message-id'] =~ /\A[<]\d+[.]FML.+[@].+[>]\z/ + return nil unless mhead['message-id'].index('.FML') > 1 dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) @@ -70,14 +64,16 @@ def inquire(mhead, mbody) # Original mail as follows: v = dscontents[-1] - if cv = e.match(/[<]([^ ]+?[@][^ ]+?)[>][.]\z/) - # Duplicated Message-ID in <2ndml@example.com>. + p1 = e.index('<') || -1 + p2 = e.rindex('>') || -1 + if p1 > 0 && p2 > 0 + # You are not a member of this mailing list . if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = e[p1 + 1, p2 - p1 - 1] v['diagnosis'] = e recipients += 1 else @@ -93,7 +89,7 @@ def inquire(mhead, mbody) e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) ErrorTable.each_key do |f| # Try to match with error messages defined in ErrorTable - next unless e['diagnosis'] =~ ErrorTable[f] + next unless ErrorTable[f].any? { |a| e['diagnosis'].include?(a) } e['reason'] = f break end diff --git a/lib/sisimai/lhost/gmail.rb b/lib/sisimai/lhost/gmail.rb index f5309c45..6a73658d 100644 --- a/lib/sisimai/lhost/gmail.rb +++ b/lib/sisimai/lhost/gmail.rb @@ -11,7 +11,6 @@ class << self message: ['Delivery to the following recipient'], error: ['The error that the other server returned was:'], }.freeze - MarkingsOf = { start: %r/Technical details of (?:permanent|temporary) failure:/ }.freeze MessagesOf = { 'expired' => [ 'DNS Error: Could not contact DNS servers', @@ -157,7 +156,6 @@ def inquire(mhead, mbody) bodyslices = emailparts[0].split("\n") readcursor = 0 # (Integer) Points the current cursor position recipients = 0 # (Integer) The number of 'Final-Recipient' header - statecode0 = 0 # (Integer) The value of (state *) in the error message v = nil while e = bodyslices.shift do @@ -187,7 +185,7 @@ def inquire(mhead, mbody) # v = dscontents[-1] - if cv = e.match(/\A[ ]+([^ ]+[@][^ ]+)\z/) + if e.start_with?(' ') && e.include?('@') # kijitora@example.jp: 550 5.2.2 ... Mailbox Full if v['recipient'] # There are multiple recipient addresses in the message body. @@ -195,7 +193,7 @@ def inquire(mhead, mbody) v = dscontents[-1] end - r = Sisimai::Address.s3s4(cv[1]) + r = Sisimai::Address.s3s4(e[e.rindex(' ') + 1, e.size]) next unless Sisimai::Address.is_emailaddress(r) v['recipient'] = r recipients += 1 @@ -226,7 +224,10 @@ def inquire(mhead, mbody) end end - if cv = e['diagnosis'].match(/[(]state[ ](\d+)[)][.]/) then statecode0 = cv[1] end + p1 = e['diagnosis'].rindex(' ') || -1 + p2 = e['diagnosis'].rindex(')') || -1 + statecode0 = e['diagnosis'][p1 + 1, p2 - p1 - 1] || 0 + if StateTable[statecode0] # (state *) e['reason'] = StateTable[statecode0]['reason'] @@ -244,7 +245,8 @@ def inquire(mhead, mbody) # Set pseudo status code e['status'] = Sisimai::SMTP::Status.find(e['diagnosis']) || '' - e['reason'] = Sisimai::SMTP::Status.name(e['status']).to_s if e['status'] =~ /\A[45][.][1-7][.][1-9]\z/ + next if e['status'].size == 0 || e['status'].include?('.0') + e['reason'] = Sisimai::SMTP::Status.name(e['status']).to_s || '' end return { 'ds' => dscontents, 'rfc822' => emailparts[1] } diff --git a/lib/sisimai/lhost/gmx.rb b/lib/sisimai/lhost/gmx.rb index 6c7c8f0c..4e704202 100644 --- a/lib/sisimai/lhost/gmx.rb +++ b/lib/sisimai/lhost/gmx.rb @@ -53,7 +53,7 @@ def inquire(mhead, mbody) # 5.1.1 ... User Unknown v = dscontents[-1] - if cv = e.match(/\A["]([^ ]+[@][^ ]+)["]:\z/) || e.match(/\A[<]([^ ]+[@][^ ]+)[>]\z/) + if e.include?('@') && ( e.start_with?('"') || e.start_with?('<') ) # "shironeko@example.jp": # ---- OR ---- # @@ -65,16 +65,16 @@ def inquire(mhead, mbody) dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = Sisimai::Address.s3s4(e) recipients += 1 elsif e.start_with?('SMTP error ') # SMTP error from remote server after RCPT command: v['command'] = Sisimai::SMTP::Command.find(e) - elsif cv = e.match(/\Ahost:[ ]*(.+)\z/) + elsif e.start_with?('host:') # host: mx.example.jp - v['rhost'] = cv[1] + v['rhost'] = e[6, e.size] else # Get error message if e =~ /\b[45][.]\d[.]\d\b/ || e =~ /[<][^ ]+[@][^ ]+[>]/ || e =~ /\b[45]\d{2}\b/ diff --git a/lib/sisimai/lhost/gsuite.rb b/lib/sisimai/lhost/gsuite.rb index 5b766685..08fed536 100644 --- a/lib/sisimai/lhost/gsuite.rb +++ b/lib/sisimai/lhost/gsuite.rb @@ -8,9 +8,8 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'].freeze MarkingsOf = { - message: %r/\A[*][*][ ].+[ ][*][*]\z/, - error: %r/\AThe[ ]response([ ]from[ ]the[ ]remote[ ]server)?[ ]was:\z/, - html: %r{\AContent-Type:[ ]text/html;[ ]charset=['"]?(?:UTF|utf)[-]8['"]?\z}, + message: ['** '], + error: ['The response was:', 'The response from the remote server was:'], }.freeze MessagesOf = { 'userunknown' => ["because the address couldn't be found. Check for typos or unnecessary spaces and try again."], @@ -28,7 +27,6 @@ def inquire(mhead, mbody) return nil unless mhead['subject'].start_with?('Delivery Status Notification') return nil unless mhead['x-gm-message-state'] - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field @@ -47,7 +45,7 @@ def inquire(mhead, mbody) # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or message/delivery-status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + readcursor |= Indicators[:deliverystatus] if MarkingsOf[:message].any? { |a| e.start_with?(a) } end next if (readcursor & Indicators[:deliverystatus]) == 0 @@ -99,10 +97,10 @@ def inquire(mhead, mbody) next unless e.start_with?(' ') v['diagnosis'] << e - elsif e =~ MarkingsOf[:error] + elsif MarkingsOf[:error].any? { |a| e.start_with?(a) } # Detect SMTP session error or connection error # The response from the remote server was: - anotherset['diagnosis'] << e + anotherset['diagnosis'] << ' ' << e else # ** Address not found ** # @@ -111,11 +109,9 @@ def inquire(mhead, mbody) # # The response from the remote server was: # 550 #5.1.0 Address rejected. - next if e =~ MarkingsOf[:html] - + next if e.start_with?('Content-Type:') if anotherset['diagnosis'] # Continued error messages from the previous line like "550 #5.1.0 Address rejected." - next if e.start_with?('Content-Type:') next if emptylines > 5 if e.empty? # Count and next() @@ -129,7 +125,7 @@ def inquire(mhead, mbody) # Your message wasn't delivered to * because the address couldn't be found. # Check for typos or unnecessary spaces and try again. next if e.empty? - next unless e =~ MarkingsOf[:message] + next unless MarkingsOf[:message].any? { |a| e.start_with?(a) } anotherset['diagnosis'] = e end end @@ -146,7 +142,7 @@ def inquire(mhead, mbody) # Copy alternative error message e['diagnosis'] = anotherset['diagnosis'] unless e['diagnosis'] - if e['diagnosis'] =~ /\A\d+\z/ + if e['diagnosis'].include?(' ') == false && e['diagnosis'].to_i > 0 e['diagnosis'] = anotherset['diagnosis'] else # More detailed error message is in "anotherset" diff --git a/lib/sisimai/lhost/imailserver.rb b/lib/sisimai/lhost/imailserver.rb index 7d15f63e..b6649350 100644 --- a/lib/sisimai/lhost/imailserver.rb +++ b/lib/sisimai/lhost/imailserver.rb @@ -8,12 +8,12 @@ class << self Boundaries = ['Original message follows.'].freeze StartingOf = { error: ['Body of message generated response:'] }.freeze ReFailures = { - 'hostunknown' => %r/Unknown host/, - 'userunknown' => %r/\A(?:Unknown user|Invalid final delivery userid)/, - 'mailboxfull' => %r/\AUser mailbox exceeds allowed size/, - 'securityerror' => %r/\ARequested action not taken: virus detected/, - 'undefined' => %r/\Aundeliverable to /, - 'expired' => %r/\ADelivery failed \d+ attempts/, + 'hostunknown' => ['Unknown host'], + 'userunknown' => ['Unknown user', 'Invalid final delivery userid'], + 'mailboxfull' => ['User mailbox exceeds allowed size'], + 'virusdetected' => ['Requested action not taken: virus detected'], + 'undefined' => ['undeliverable to'], + 'expired' => ['Delivery failed '], }.freeze # Parse bounce messages from IMailServer @@ -54,14 +54,14 @@ def inquire(mhead, mbody) v['recipient'] = cv[3] recipients += 1 - elsif cv = e.match(/\Aundeliverable[ ]+to[ ]+(.+)\z/) + elsif e.start_with?('undeliverable ') # undeliverable to kijitora@example.com if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = Sisimai::Address.s3s4(cv[1]) + v['recipient'] = Sisimai::Address.s3s4(e) recipients += 1 else # Other error message text @@ -86,10 +86,9 @@ def inquire(mhead, mbody) e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || '' e['command'] = Sisimai::SMTP::Command.find(e['diagnosis']) - ReFailures.each_key do |r| # Verify each regular expression of session errors - next unless e['diagnosis'] =~ ReFailures[r] + next unless ReFailures[r].any? { |a| e['diagnosis'].include?(a) } e['reason'] = r break end diff --git a/lib/sisimai/lhost/interscanmss.rb b/lib/sisimai/lhost/interscanmss.rb index a6808f78..4fb00c28 100644 --- a/lib/sisimai/lhost/interscanmss.rb +++ b/lib/sisimai/lhost/interscanmss.rb @@ -36,19 +36,21 @@ def inquire(mhead, mbody) # line of the beginning of the original message. next if e.empty? - v = dscontents[-1] - if cv = e.match(/\A.+[<>]{3}[ ]+.+[<]([^ ]+[@][^ ]+)[>]\z/) || - e.match(/\A.+[<>]{3}[ ]+.+[<]([^ ]+[@][^ ]+)[>]/) || - e.match(/\A(?:Reason:[ ]+)?Unable[ ]to[ ]deliver[ ]message[ ]to[ ][<](.+)[>]/) + v = dscontents[-1] + p1 = e.index(' <<< ') || -1 # Sent <<< ... + p2 = e.index(' >>> ') || -1 # Received >>> ... + if e.include?('@') && e.include?(' <') && ( p1 > 1 || p2 > 1 || e.include?('Unable to deliver ') ) # Sent <<< RCPT TO: # Received >>> 550 5.1.1 ... user unknown # Unable to deliver message to - if v['recipient'] && cv[1] != v['recipient'] + cr = e[e.rindex('<') + 1, e.rindex('>') - e.rindex('<') - 1] + + if v['recipient'] && cr != v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = cr v['diagnosis'] = e if e.include?('Unable to deliver ') recipients = dscontents.size end @@ -57,18 +59,15 @@ def inquire(mhead, mbody) # Sent <<< RCPT TO: v['command'] = Sisimai::SMTP::Command.find(e) - elsif cv = e.match(/\AReceived[ ]+[>]{3}[ ]+(\d{3}[ ]+.+)\z/) + elsif e.start_with?('Received >>> ') # Received >>> 550 5.1.1 ... user unknown - v['diagnosis'] = cv[1] + v['diagnosis'] = e[e.index(' >>> ') + 4, e.size] else # Error message in non-English - next unless e =~ /[ ][<>]{3}[ ]/ v['command'] = Sisimai::SMTP::Command.find(e) if e.include?(' >>> ') - - if cv = e.match(/[ ][<]{3}[ ](.+)/) - # <<< 550 5.1.1 User unknown - v['diagnosis'] = cv[1] - end + p3 = e.index(' <<< ') + next unless p3 + v['diagnosis'] = e[p3 + 4, e.size] end end return nil unless recipients > 0 diff --git a/lib/sisimai/lhost/kddi.rb b/lib/sisimai/lhost/kddi.rb index b0d2f176..935c0c96 100644 --- a/lib/sisimai/lhost/kddi.rb +++ b/lib/sisimai/lhost/kddi.rb @@ -7,13 +7,7 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['Content-Type: message/rfc822'].freeze - MarkingsOf = { - message: %r/\AYour[ ]mail[ ](?: - sent[ ]on:?[ ][A-Z][a-z]{2}[,] - |attempted[ ]to[ ]be[ ]delivered[ ]on:?[ ][A-Z][a-z]{2}[,] - ) - /x, - }.freeze + MarkingsOf = { message: ['Your mail sent on:', 'Your mail attempted to be delivered on:'] }.freeze MessagesOf = { 'mailboxfull' => ['As their mailbox is full'], 'norelaying' => ['Due to the following SMTP relay error'], @@ -28,7 +22,7 @@ class << self def inquire(mhead, mbody) # :'message-id' => %r/[@].+[.]ezweb[.]ne[.]jp[>]\z/, match = 0 - match += 1 if mhead['from'] =~ /no-reply[@].+[.]dion[.]ne[.]jp/ + match += 1 if Sisimai::String.aligned(mhead['from'], ['no-reply@.', '.dion.ne.jp']) match += 1 if mhead['reply-to'].to_s == 'no-reply@app.auone-net.jp' match += 1 if mhead['received'].any? { |a| a.include?('ezweb.ne.jp (') } match += 1 if mhead['received'].any? { |a| a.include?('.au.com (') } @@ -46,14 +40,14 @@ def inquire(mhead, mbody) # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + readcursor |= Indicators[:deliverystatus] if MarkingsOf[:message].any? { |a| e.start_with?(a) } next end next if (readcursor & Indicators[:deliverystatus]) == 0 next if e.empty? v = dscontents[-1] - if cv = e.match(/\A[ ]+Could not be delivered to: [<]([^ ]+[@][^ ]+)[>]/) + if e.include?(' Could not be delivered to: <') # Your mail sent on: Thu, 29 Apr 2010 11:04:47 +0900 # Could not be delivered to: <******@**.***.**> # As their mailbox is full. @@ -62,14 +56,14 @@ def inquire(mhead, mbody) dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - r = Sisimai::Address.s3s4(cv[1]) + r = Sisimai::Address.s3s4(e[e.index('<') + 1, e.size]) next unless Sisimai::Address.is_emailaddress(r) v['recipient'] = r recipients += 1 - elsif cv = e.match(/Your mail sent on: (.+)\z/) + elsif e.include?('Your mail sent on: ') # Your mail sent on: Thu, 29 Apr 2010 11:04:47 +0900 - v['date'] = cv[1] + v['date'] = e[19, e.size] else # As their mailbox is full. v['diagnosis'] ||= '' diff --git a/lib/sisimai/lhost/mailfoundry.rb b/lib/sisimai/lhost/mailfoundry.rb index 94c5ffb7..012e2300 100644 --- a/lib/sisimai/lhost/mailfoundry.rb +++ b/lib/sisimai/lhost/mailfoundry.rb @@ -45,14 +45,14 @@ def inquire(mhead, mbody) # This has been a permanent failure. No further delivery attempts will be made. v = dscontents[-1] - if cv = e.match(/\AUnable to deliver message to: [<]([^ ]+[@][^ ]+)[>]\z/) + if e.start_with?('Unable to deliver message to: <') && e.index('@') > 1 # Unable to deliver message to: if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = e[e.index('<'), e.size] recipients += 1 else # Error message diff --git a/lib/sisimai/lhost/mailmarshalsmtp.rb b/lib/sisimai/lhost/mailmarshalsmtp.rb index 37720346..381daa39 100644 --- a/lib/sisimai/lhost/mailmarshalsmtp.rb +++ b/lib/sisimai/lhost/mailmarshalsmtp.rb @@ -52,7 +52,7 @@ def inquire(mhead, mbody) # dummyuser@blabla.xxxxxxxxxxxx.com v = dscontents[-1] - if cv = e.match(/\A[ ]{4}([^ ]+[@][^ ]+)\z/) + if e.start_with?(' ') && e.index('@') > 1 # The following recipients were affected: # dummyuser@blabla.xxxxxxxxxxxx.com if v['recipient'] @@ -60,7 +60,7 @@ def inquire(mhead, mbody) dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = e[4, e.size] recipients += 1 else # Get error message lines @@ -84,23 +84,29 @@ def inquire(mhead, mbody) # Reporting-MTA: # MessageName: # Last-Attempt-Date: <16:21:07 seg, 22 Dezembro 2014> - if cv = e.match(/\AOriginal Sender:[ ]+[<](.+)[>]\z/) + p1 = e.index('<') + p2 = e.index('>') + if e.start_with?('Original Sender: ') # Original Sender: # Use this line instead of "From" header of the original message. - emailparts[1] << ('From: ' << cv[1] << "\n") + emailparts[1] << ('From: ' << e[p1 + 1, p2 - p1 - 1] << "\n") - elsif cv = e.match(/\ASender-MTA:[ ]+[<](.+)[>]\z/) + elsif e.start_with?('Sender-MTA: ') # Sender-MTA: <10.11.12.13> - v['lhost'] = cv[1] + v['lhost'] = e[p1 + 1, p2 - p1 - 1] - elsif cv = e.match(/\AReporting-MTA:[ ]+[<](.+)[>]\z/) + elsif e.start_with?('Reporting-MTA: ') # Reporting-MTA: - v['rhost'] = cv[1] + v['rhost'] = e[p1 + 1, p2 - p1 - 1] - elsif cv = e.match(/\A\s+(From|Subject):\s*(.+)\z/) + elsif e.include?(' From:') || e.include?(' Subject:') # From: originalsender@example.com # Subject: ... - emailparts[1] << sprintf("%s: %s\n", cv[1], cv[2]) + p1 = e.index(' From:') || e.index(' Subject:') + p2 = e.index(':') + cf = e[p1 + 1, p2 - p1 - 1] + cv = Sisimai::String.sweep(e[p2 + 1, e.size]) + emailparts[1] << sprintf("%s: %s\n", cf, cv) end end end diff --git a/lib/sisimai/lhost/mailru.rb b/lib/sisimai/lhost/mailru.rb index 78fd106c..155d521d 100644 --- a/lib/sisimai/lhost/mailru.rb +++ b/lib/sisimai/lhost/mailru.rb @@ -50,17 +50,19 @@ class << self # @return [Hash] Bounce data list and message/rfc822 part # @return [Nil] it failed to parse or the arguments are missing def inquire(mhead, mbody) - return nil unless mhead['from'] =~ /[<]?mailer-daemon[@].*mail[.]ru[>]?/i - return nil unless mhead['message-id'].end_with?('.mail.ru>', 'smailru.net>') - return nil unless mhead['subject'] =~ %r{(?: - Mail[ ]delivery[ ]failed(:[ ]returning[ ]message[ ]to[ ]sender)? - |Warning:[ ]message[ ].+[ ]delayed[ ]+ - |Delivery[ ]Status[ ]Notification - |Mail[ ]failure - |Message[ ]frozen - |error[(]s[)][ ]in[ ]forwarding[ ]or[ ]filtering - ) - }x + mfrom = mhead['from'].downcase + msgid = mhead['message-id'] ? mhead['message-id'].downcase : '' + match = 0 + match += 1 if mfrom.include?('mailer-daemon@') && mfrom.include?('mail.ru') + match += 1 if msgid.end_with?('.mail.ru>', 'smailru.net>') + match += 1 if [ + 'Delivery Status Notification', + 'Mail delivery failed', + 'Mail failure', + 'Message frozen', + 'Warning: message ', + 'error(s) in forwarding or filtering'].any? { |a| mhead['subject'].include?(a) } + return nil unless match > 2 dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) @@ -101,14 +103,14 @@ def inquire(mhead, mbody) # host neko.example.jp [192.0.2.222]: 550 5.1.1 ... User Unknown v = dscontents[-1] - if cv = e.match(/\A[ ]+([^ ]+[@][^ ]+[.][a-zA-Z]+)\z/) + if e.start_with?(' ') && e.include?(' ') == false && e.index('@') > 1 # kijitora@example.jp if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = e[2, e.size] recipients += 1 elsif dscontents.size == recipients diff --git a/lib/sisimai/lhost/mcafee.rb b/lib/sisimai/lhost/mcafee.rb index f3115218..b2201b0b 100644 --- a/lib/sisimai/lhost/mcafee.rb +++ b/lib/sisimai/lhost/mcafee.rb @@ -8,15 +8,7 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['Content-Type: message/rfc822'].freeze StartingOf = { message: ['--- The following addresses had delivery problems ---'] }.freeze - ReFailures = { - 'userunknown' => %r{(?: - [ ]User[ ][(].+[@].+[)][ ]unknown[.][ ] - |550[ ]Unknown[ ]user[ ][^ ]+[@][^ ]+ - |550[ ][<].+?[@].+?[>][.]+[ ]User[ ]not[ ]exist - |No[ ]such[ ]user - ) - }x, - }.freeze + MessagesOf = { 'userunknown' => [' User not exist', ' unknown.', '550 Unknown user ', 'No such user'] }.freeze # Parse bounce messages from McAfee Email Appliance # @param [Hash] mhead Message headers of a bounce email @@ -29,7 +21,6 @@ def inquire(mhead, mbody) return nil unless mhead['x-nai-header'].start_with?('Modified by McAfee ') return nil unless mhead['subject'] == 'Delivery Status' - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) @@ -37,7 +28,7 @@ def inquire(mhead, mbody) readslices = [''] readcursor = 0 # (Integer) Points the current cursor position recipients = 0 # (Integer) The number of 'Final-Recipient' header - diagnostic = '' # (String) Alternative diagnostic message + issuedcode = '' # (String) Alternative diagnostic message v = nil while e = bodyslices.shift do @@ -64,15 +55,15 @@ def inquire(mhead, mbody) # v = dscontents[-1] - if cv = e.match(/\A[<]([^ ]+[@][^ ]+)[>][ ]+[(](.+)[)]\z/) + if Sisimai::String.aligned(e, ['<', '@', '>', '(', ')']) # (Unknown user kijitora@example.co.jp) if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] - diagnostic = cv[2] + v['recipient'] = Sisimai::Address.s3s4(e[e.index('<'), e.index('>')]) + issuedcode = e[e.index('(') + 1, e.size] recipients += 1 elsif f = Sisimai::RFC1894.match(e) @@ -81,8 +72,8 @@ def inquire(mhead, mbody) unless o # Fallback code for empty value or invalid formatted value # - Original-Recipient: - if cv = e.match(/\AOriginal-Recipient:[ ]([^ ]+)\z/) - v['alias'] = Sisimai::Address.s3s4(cv[1]) + if e.start_with?('Original-Recipient: ') + v['alias'] = Sisimai::Address.s3s4(e[e.index(':') + 1, e.size]) end next end @@ -92,18 +83,18 @@ def inquire(mhead, mbody) else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e end end return nil unless recipients > 0 dscontents.each do |e| - e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'] || diagnostic) - ReFailures.each_key do |r| + e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'] || issuedcode) + MessagesOf.each_key do |r| # Verify each regular expression of session errors - next unless e['diagnosis'] =~ ReFailures[r] + next unless MessagesOf[r].any? { |a| e['diagnosis'].include?(a) } e['reason'] = r break end diff --git a/lib/sisimai/lhost/messagelabs.rb b/lib/sisimai/lhost/messagelabs.rb index 69417104..87dfe664 100644 --- a/lib/sisimai/lhost/messagelabs.rb +++ b/lib/sisimai/lhost/messagelabs.rb @@ -8,9 +8,9 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['Content-Type: text/rfc822-headers'].freeze StartingOf = { message: ['Content-Type: message/delivery-status'] }.freeze - ReFailures = { - 'userunknown' => %r/(?:542 .+ Rejected|No such user)/, - 'securityerror' => %r/Please turn on SMTP Authentication in your mail client/, + MessagesOf = { + 'userunknown' => ['542 ', ' Rejected', 'No such user'], + 'securityerror' => ['Please turn on SMTP Authentication in your mail client'], }.freeze # Parse bounce messages from Symantec.cloud(MessageLabs) @@ -28,7 +28,6 @@ def inquire(mhead, mbody) return nil unless mhead['from'].include?('MAILER-DAEMON@messagelabs.com') return nil unless mhead['subject'].start_with?('Mail Delivery Failure') - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field @@ -90,8 +89,8 @@ def inquire(mhead, mbody) else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e end end @@ -104,9 +103,9 @@ def inquire(mhead, mbody) e['command'] = commandset.shift || '' e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) - ReFailures.each_key do |r| + MessagesOf.each_key do |r| # Verify each regular expression of session errors - next unless e['diagnosis'] =~ ReFailures[r] + next unless MessagesOf[r].any? { |a| e['diagnosis'].include?(a) } e['reason'] = r break end diff --git a/lib/sisimai/lhost/messagingserver.rb b/lib/sisimai/lhost/messagingserver.rb index 88785b91..10cf171e 100644 --- a/lib/sisimai/lhost/messagingserver.rb +++ b/lib/sisimai/lhost/messagingserver.rb @@ -16,7 +16,6 @@ class << self # @return [Hash] Bounce data list and message/rfc822 part # @return [Nil] it failed to parse or the arguments are missing def inquire(mhead, mbody) - # :received => %r/[ ][(]MessagingServer[)][ ]with[ ]/, match = 0 match += 1 if mhead['content-type'].include?('Boundary_(ID_') match += 1 if mhead['subject'].start_with?('Delivery Notification: ') @@ -60,45 +59,49 @@ def inquire(mhead, mbody) # Remote system: dns;mx.example.jp (TCP|17.111.174.67|47323|192.0.2.225|25) (6jo.example.jp ESMTP SENDMAIL-VM) v = dscontents[-1] - if cv = e.match(/\A[ ]+Recipient address:[ ]*([^ ]+[@][^ ]+)\z/) + if e.start_with?(' Recipient address: ') && e.index('@') > 1 # Recipient address: kijitora@example.jp if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = Sisimai::Address.s3s4(cv[1]) + v['recipient'] = Sisimai::Address.s3s4(e[e.rindex(' ') + 1, e.size]) recipients += 1 - elsif cv = e.match(/\A[ ]+Original address:[ ]*([^ ]+[@][^ ]+)\z/) + elsif e.start_with?(' Original address: ') && e.index('@') > 1 # Original address: kijitora@example.jp - v['recipient'] = Sisimai::Address.s3s4(cv[1]) + v['recipient'] = Sisimai::Address.s3s4(e[e.rindex(' ') + 1, e.size]) - elsif cv = e.match(/\A[ ]+Date:[ ]*(.+)\z/) + elsif e.start_with?(' Date: ') # Date: Fri, 21 Nov 2014 23:34:45 +0900 - v['date'] = cv[1] + v['date'] = e[e.index(':') + 2, e.size] - elsif cv = e.match(/\A[ ]+Reason:[ ]*(.+)\z/) + elsif e.start_with?(' Reason: ') # Reason: Remote SMTP server has rejected address - v['diagnosis'] = cv[1] + v['diagnosis'] = e[e.index(':') + 2, e.size] - elsif cv = e.match(/\A[ ]+Diagnostic code:[ ]*([^ ]+);(.+)\z/) + elsif e.start_with?(' Diagnostic code: ') # Diagnostic code: smtp;550 5.1.1 ... User Unknown - v['spec'] = cv[1].upcase - v['diagnosis'] = cv[2] + p1 = e.index(':') + p2 = e.index(';') + v['spec'] = e[p1 + 2, p2 - p1 - 2].upcase + v['diagnosis'] = e[p2 + 1, e.size] - elsif cv = e.match(/\A[ ]+Remote system:[ ]*dns;([^ ]+)[ ]*([^ ]+)[ ]*.+\z/) + elsif e.start_with?(' Remote system: ') # Remote system: dns;mx.example.jp (TCP|17.111.174.67|47323|192.0.2.225|25) # (6jo.example.jp ESMTP SENDMAIL-VM) - remotehost = cv[1] # remote host - sessionlog = cv[2] # smtp session + p1 = e.index(';') + p2 = e.index('(') + remotehost = e[p1 + 1, p2 - p1 - 2] + sessionlog = e[p2, e.size].split('|') v['rhost'] = remotehost # The value does not include ".", use IP address instead. # (TCP|17.111.174.67|47323|192.0.2.225|25) - next unless cv = sessionlog.match(/\A[(]TCP|(.+)|\d+|(.+)|\d+[)]/) - v['lhost'] = cv[1] - v['rhost'] = cv[2] unless remotehost =~ /[^.]+[.][^.]+/ + next unless sessionlog[0] == '(TCP' + v['lhost'] = sessionlog[1] + v['rhost'] = sessionlog[3] unless remotehost.index('.') > 1 else # Original-envelope-id: 0NFC009FLKOUVMA0@mr21p30im-asmtp004.me.com # Reporting-MTA: dns;mr21p30im-asmtp004.me.com (tcp-daemon) @@ -112,20 +115,22 @@ def inquire(mhead, mbody) # (6jo.example.jp ESMTP SENDMAIL-VM) # Diagnostic-code: smtp;550 5.1.1 ... User Unknown # - if cv = e.match(/\AStatus:[ ]*(\d[.]\d[.]\d)[ ]*[(](.+)[)]\z/) + if e.start_with?('Status: ') # Status: 5.1.1 (Remote SMTP server has rejected address) - v['status'] = cv[1] - v['diagnosis'] ||= cv[2] + p1 = e.index(':') + p2 = e.index('(') + v['status'] = e[p1 + 2, p2 - p1 - 3] + v['diagnosis'] ||= e[p2 + 1, e[e.index(')') - p2 - 1]] - elsif cv = e.match(/\AArrival-Date:[ ](.+)\z/) + elsif e.start_with?('Arrival-Date: ') # Arrival-date: Thu, 29 Apr 2014 23:34:45 +0000 (GMT) - v['date'] ||= cv[1] + v['date'] ||= e[e.index(':') + 2, e.size] - elsif cv = e.match(/\AReporting-MTA:[ ]dns;[ ](.+)\z/) + elsif e.start_with?('Reporting-MTA: ') # Reporting-MTA: dns;mr21p30im-asmtp004.me.com (tcp-daemon) - localhost = cv[1] + localhost = e[e.index(';') + 1, e.size] v['lhost'] ||= localhost - v['lhost'] = localhost unless v['lhost'] =~ /[^.]+[.][^ ]+/ + v['lhost'] = localhost unless v['lhost'].index('.') > 0 end end end diff --git a/lib/sisimai/lhost/mfilter.rb b/lib/sisimai/lhost/mfilter.rb index 05f31f83..fd77fb9f 100644 --- a/lib/sisimai/lhost/mfilter.rb +++ b/lib/sisimai/lhost/mfilter.rb @@ -11,7 +11,6 @@ class << self error: ['-------server message'], command: ['-------SMTP command'], }.freeze - MarkingsOf = { message: %r/\A[^ ]+[@][^ ]+[.][a-zA-Z]+\z/ }.freeze # Parse bounce messages from Digital Arts m-FILTER # @param [Hash] mhead Message headers of a bounce email @@ -36,7 +35,9 @@ def inquire(mhead, mbody) # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + if e.include?('@') && e.include?(' ') == false && Sisimai::Address.is_emailaddress(e) + readcursor |= Indicators[:deliverystatus] + end end next if (readcursor & Indicators[:deliverystatus]) == 0 next if e.empty? @@ -58,7 +59,7 @@ def inquire(mhead, mbody) # -------original message v = dscontents[-1] - if cv = e.match(/\A([^ ]+[@][^ ]+)\z/) + if e.include?('@') && e.include?(' ') == false # 以下のメールアドレスへの送信に失敗しました。 # kijitora@example.jp if v['recipient'] @@ -66,10 +67,10 @@ def inquire(mhead, mbody) dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = e recipients += 1 - elsif e =~ /\A[A-Z]{4}/ + elsif e.size == 4 && e.index(' ').nil? # -------SMTP command # DATA next if v['command'] diff --git a/lib/sisimai/lhost/mxlogic.rb b/lib/sisimai/lhost/mxlogic.rb index 7b9274b9..64e585c7 100644 --- a/lib/sisimai/lhost/mxlogic.rb +++ b/lib/sisimai/lhost/mxlogic.rb @@ -81,12 +81,7 @@ def inquire(mhead, mbody) match += 1 if mhead['x-mxl-hash'] match += 1 if mhead['x-mxl-notehash'] match += 1 if mhead['from'].start_with?('Mail Delivery System') - match += 1 if mhead['subject'] =~ %r{(?: - Mail[ ]delivery[ ]failed(:[ ]returning[ ]message[ ]to[ ]sender)? - |Warning:[ ]message[ ][^ ]+[ ]delayed[ ]+ - |Delivery[ ]Status[ ]Notification - ) - }x + match += 1 if ['Delivery Status Notification', 'Mail delivery failed', 'Warning: message '].any? { |a| mhead['subject'].include?(a) } return nil unless match > 0 dscontents = [Sisimai::Lhost.DELIVERYSTATUS] @@ -118,7 +113,7 @@ def inquire(mhead, mbody) # host neko.example.jp [192.0.2.222]: 550 5.1.1 ... User Unknown v = dscontents[-1] - if cv = e.match(/\A[ ]*[<]([^ ]+[@][^ ]+)[>]:(.+)\z/) + if e.start_with?(' <') && e.include?('@') && e.include?('>:') # A message that you have sent could not be delivered to one or more # recipients. This is a permanent error. The following address failed: # @@ -128,8 +123,8 @@ def inquire(mhead, mbody) dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] - v['diagnosis'] = cv[2] + v['recipient'] = e[3, e.index('>:') - 3] + v['diagnosis'] = e[e.index('>:') + 3, e.size] recipients += 1 elsif dscontents.size == recipients @@ -142,8 +137,13 @@ def inquire(mhead, mbody) unless mhead['received'].empty? # Get the name of local MTA - # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128]) - if cv = mhead['received'][-1].match(/from[ ]([^ ]+) /) then localhost0 = cv[1] end + p1 = mhead['received'][-1].downcase.index('from ') + p2 = mhead['received'][-1].index(' ', p1 + 5) + + if (p1 + 1) * (p2 + 1) > 0 + # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128]) + localhost0 = mhead['received'][-1][p1 + 5, p2 - p1 - 5] + end end dscontents.each do |e| @@ -152,8 +152,11 @@ def inquire(mhead, mbody) unless e['rhost'] # Get the remote host name + p1 = e['diagnosis'].index('host ') || -1 + p2 = e['diagnosis'].index(' ', p1 + 5) + # host neko.example.jp [192.0.2.222]: 550 5.1.1 ... User Unknown - if cv = e['diagnosis'].match(/host[ ]+([^ ]+)[ ]\[.+\]:[ ]/) then e['rhost'] = cv[1] end + e['rhost'] = e['diagnosis'][p1 + 5, p2 - p1 - 5] if p1 > -1 unless e['rhost'] # Get localhost and remote host name from Received header. diff --git a/lib/sisimai/lhost/notes.rb b/lib/sisimai/lhost/notes.rb index 67473d10..ff10a618 100644 --- a/lib/sisimai/lhost/notes.rb +++ b/lib/sisimai/lhost/notes.rb @@ -34,10 +34,9 @@ def inquire(mhead, mbody) encodedmsg = '' v = nil - if cv = mhead['content-type'].match(/\A.+;[ ]*charset=(.+)\z/) - # Get character set name - # Content-Type: text/plain; charset=ISO-2022-JP - characters = cv[1].downcase + if mhead['content-type'].include?('charset=') + # Get character set name, Content-Type: text/plain; charset=ISO-2022-JP + characters = mhead['content-type'][mhead['content-type'].index('charset=') + 8, mhead['content-type'].size].downcase end while e = bodyslices.shift do @@ -57,7 +56,7 @@ def inquire(mhead, mbody) # # ------- Returned Message -------- v = dscontents[-1] - if e =~ /\A[^ ]+[@][^ ]+/ + if e.include?('@') && e.index(' ').nil? # kijitora@notes.example.jp if v['recipient'] # There are multiple recipient addresses in the message body. @@ -96,8 +95,10 @@ def inquire(mhead, mbody) unless recipients > 0 # Fallback: Get the recpient address from RFC822 part - if cv = emailparts[1].match(/^To:[ ]*(.+)$/) - v['recipient'] = Sisimai::Address.s3s4(cv[1]) + p1 = emailparts[1].index("\nTo: ") || -1 + p2 = emailparts[1].index("\n", p1 + 6) || -1 + if p1 > 0 + v['recipient'] = Sisimai::Address.s3s4(emailparts[1][p1 + 5, p2 - p1 - 5]) || '' recipients += 1 unless v['recipient'].empty? end end diff --git a/lib/sisimai/lhost/opensmtpd.rb b/lib/sisimai/lhost/opensmtpd.rb index 10761a7a..2e2e36cf 100644 --- a/lib/sisimai/lhost/opensmtpd.rb +++ b/lib/sisimai/lhost/opensmtpd.rb @@ -104,15 +104,15 @@ def inquire(mhead, mbody) # Below is a copy of the original message: v = dscontents[-1] - if cv = e.match(/\A([^ ]+?[@][^ ]+?):?[ ](.+)\z/) + if e.include?('@') && Sisimai::String.aligned(e, ['@', ' ']) # kijitora@example.jp: 550 5.2.2 ... Mailbox Full if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] - v['diagnosis'] = cv[2] + v['recipient'] = e[0, e.index(':')] + v['diagnosis'] = e[e.index(':') + 1, e.size] recipients += 1 end end diff --git a/lib/sisimai/lhost/outlook.rb b/lib/sisimai/lhost/outlook.rb index 9dd19e1d..f16dab56 100644 --- a/lib/sisimai/lhost/outlook.rb +++ b/lib/sisimai/lhost/outlook.rb @@ -28,7 +28,6 @@ def inquire(mhead, mbody) match += 1 if mhead['received'].any? { |a| a.include?('.hotmail.com') } return nil if match < 2 - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field @@ -89,8 +88,8 @@ def inquire(mhead, mbody) else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e end end diff --git a/lib/sisimai/lhost/postfix.rb b/lib/sisimai/lhost/postfix.rb index 4badfbf1..34a3a0c7 100644 --- a/lib/sisimai/lhost/postfix.rb +++ b/lib/sisimai/lhost/postfix.rb @@ -149,52 +149,54 @@ def inquire(mhead, mbody) # # : host mx.example.co.jp[192.0.2.153] said: 550 # 5.1.1 ... User Unknown (in reply to RCPT TO command) - if readslices[-2].start_with?('Diagnostic-Code:') && cv = e.match(/\A[ ]+(.+)\z/) + if readslices[-2].start_with?('Diagnostic-Code:') && e.include?(' ') # Continued line of the value of Diagnostic-Code header - v['diagnosis'] << ' ' << cv[1] + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e - elsif cv = e.match(/\A(X-Postfix-Sender):[ ]*rfc822;[ ]*(.+)\z/) + elsif Sisimai::String.aligned(e, ['X-Postfix-Sender:', 'rfc822;', '@']) # X-Postfix-Sender: rfc822; shironeko@example.org - emailparts[1] << cv[1] << ': ' << cv[2] << "\n" + emailparts[1] << 'X-Postfix-Sender: ' << Sisimai::Address.s3s4(e[e.index(';') + 1, e.size]) << "\n" else + # Alternative error message and recipient if e.include?(' (in reply to ') || e.include?('command)') # 5.1.1 ... User Unknown (in reply to RCPT TO q = Sisimai::SMTP::Command.find(e); commandset << q if q anotherset['diagnosis'] ||= '' anotherset['diagnosis'] << ' ' << e + + elsif Sisimai::String.aligned(e, ['<', '@', '>', '(expanded from<', '):']) + # (expanded from ): user ... + p1 = e.index('> ') + p2 = e.index('(expanded from ', p1) + p3 = e.index('>): ', p2 + 14) + anotherset['recipient'] = Sisimai::Address.s3s4(e[0, p1]) + anotherset['alias'] = Sisimai::Address.s3s4(e[p2 + 15, p3 - p2 - 15]) + anotherset['diagnosis'] = e[p3 + 3, e.size] + + elsif e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>:']) + # : ... + anotherset['recipient'] = Sisimai::Address.s3s4(e[0, e.index('>')]) + anotherset['diagnosis'] = e[e.index('>:') + 2, e.size] + + elsif e.include?('--- Delivery report unavailable ---') + # postfix-3.1.4/src/bounce/bounce_notify_util.c + # bounce_notify_util.c:602|if (bounce_info->log_handle == 0 + # bounce_notify_util.c:602||| bounce_log_rewind(bounce_info->log_handle)) { + # bounce_notify_util.c:602|if (IS_FAILURE_TEMPLATE(bounce_info->template)) { + # bounce_notify_util.c:602| post_mail_fputs(bounce, ""); + # bounce_notify_util.c:602| post_mail_fputs(bounce, "\t--- delivery report unavailable ---"); + # bounce_notify_util.c:602| count = 1; /* xxx don't abort */ + # bounce_notify_util.c:602|} + # bounce_notify_util.c:602|} else { + nomessages = true else - # Alternative error message and recipient - if cv = e.match(/\A[<]([^ ]+[@][^ ]+)[>] [(]expanded from [<](.+)[>][)]:[ ]*(.+)\z/) - # (expanded from ): user ... - anotherset['recipient'] = cv[1] - anotherset['alias'] = cv[2] - anotherset['diagnosis'] = cv[3] - - elsif cv = e.match(/\A[<]([^ ]+[@][^ ]+)[>]:(.*)\z/) - # : ... - anotherset['recipient'] = cv[1] - anotherset['diagnosis'] = cv[2] - - elsif e.include?('--- Delivery report unavailable ---') - # postfix-3.1.4/src/bounce/bounce_notify_util.c - # bounce_notify_util.c:602|if (bounce_info->log_handle == 0 - # bounce_notify_util.c:602||| bounce_log_rewind(bounce_info->log_handle)) { - # bounce_notify_util.c:602|if (IS_FAILURE_TEMPLATE(bounce_info->template)) { - # bounce_notify_util.c:602| post_mail_fputs(bounce, ""); - # bounce_notify_util.c:602| post_mail_fputs(bounce, "\t--- delivery report unavailable ---"); - # bounce_notify_util.c:602| count = 1; /* xxx don't abort */ - # bounce_notify_util.c:602|} - # bounce_notify_util.c:602|} else { - nomessages = true - else - # Get error message continued from the previous line - next unless anotherset['diagnosis'] - if e.start_with?(' ') - # host mx.example.jp said:... - anotherset['diagnosis'] << ' ' << e[4, e.size] - end + # Get an error message continued from the previous line + next unless anotherset['diagnosis'] + if e.start_with?(' ') + # host mx.example.jp said:... + anotherset['diagnosis'] << ' ' << e[4, e.size] end end end @@ -212,9 +214,11 @@ def inquire(mhead, mbody) else # Get a recipient address from message/rfc822 part if the delivery report was unavailable: # '--- Delivery report unavailable ---' - if nomessages && cv = emailparts[1].match(/^To:[ ](.+)$/) + p1 = emailparts[1].index("\nTo: ") || -1 + p2 = emailparts[1].index("\n", p1 + 6) || -1 + if nomessages && p1 > 0 # Try to get a recipient address from To: field in the original message at message/rfc822 part - dscontents[-1]['recipient'] = Sisimai::Address.s3s4(cv[1]) + dscontents[-1]['recipient'] = Sisimai::Address.s3s4(emailparts[1][p1 + 5, p2 - p1 - 5]) recipients += 1 end end @@ -228,39 +232,47 @@ def inquire(mhead, mbody) if anotherset['diagnosis'] # Copy alternative error message - e['diagnosis'] = anotherset['diagnosis'] unless e['diagnosis'] + anotherset['diagnosis'] = Sisimai::String.sweep(anotherset['diagnosis']) + e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].nil? || e['diagnosis'].empty? if e['diagnosis'] =~ /\A\d+\z/ + # Override the value of diagnostic code message e['diagnosis'] = anotherset['diagnosis'] else # More detailed error message is in "anotherset" - as = nil # status - ar = nil # replycode + as = '' # status + ar = '' # replycode e['status'] ||= '' e['replycode'] ||= '' - if e['status'] == '' || e['status'].start_with?('4.0.0', '5.0.0') + if e['status'].empty? || e['status'].start_with?('4.0.0', '5.0.0') # Check the value of D.S.N. in anotherset - as = Sisimai::SMTP::Status.find(anotherset['diagnosis']) - if as.size > 0 && as[-3, 3] != '0.0' + as = Sisimai::SMTP::Status.find(anotherset['diagnosis']) || '' + if as.size > 0 && as[-4, 4] != '.0.0' # The D.S.N. is neither an empty nor *.0.0 e['status'] = as end end - if e['replycode'] == '' || e['replycode'].start_with?('400', '500') - # Check the value of SMTP reply code in anotherset - ar = Sisimai::SMTP::Reply.find(anotherset['diagnosis']) - if ar && ar[-2, 2].to_i != 0 + if e['replycode'].empty? || e['replycode'].end_with?('00') + # Check the value of SMTP reply code in $anotherset + ar = Sisimai::SMTP::Reply.find(anotherset['diagnosis']) || '' + if ar.size > 0 && ar.end_with?('00') == false # The SMTP reply code is neither an empty nor *00 e['replycode'] = ar end end - if (as || ar) && (anotherset['diagnosis'].size > e['diagnosis'].size) - # Update the error message in e['diagnosis'] + while true + # Replace e['diagnosis'] with the value of anotherset['diagnosis'] when all the + # following conditions have not matched. + break if (as + ar).size == 0 + break if anotherset['diagnosis'].size < e['diagnosis'].size + break if anotherset['diagnosis'].include?(e['diagnosis']) == false + e['diagnosis'] = anotherset['diagnosis'] + break end end end diff --git a/lib/sisimai/lhost/powermta.rb b/lib/sisimai/lhost/powermta.rb index cc893f53..2a68a4a6 100644 --- a/lib/sisimai/lhost/powermta.rb +++ b/lib/sisimai/lhost/powermta.rb @@ -29,7 +29,6 @@ class << self def inquire(mhead, mbody) return nil unless mhead['subject'].to_s.start_with?('Delivery report') - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field @@ -95,9 +94,9 @@ def inquire(mhead, mbody) # # delivery failed; will not continue trying # - if cv = e.match(/\AX-PowerMTA-BounceCategory:[ ]*(.+)\z/) + if e.start_with?('X-PowerMTA-BounceCategory: ') # X-PowerMTA-BounceCategory: bad-mailbox - v['category'] = cv[1] + v['category'] = e[e.index(':') + 2, e.size] end end end diff --git a/lib/sisimai/lhost/qmail.rb b/lib/sisimai/lhost/qmail.rb index 566df666..63c6a950 100644 --- a/lib/sisimai/lhost/qmail.rb +++ b/lib/sisimai/lhost/qmail.rb @@ -15,44 +15,33 @@ class << self # # Characters: K,Z,D in qmail-qmqpc.c, qmail-send.c, qmail-rspawn.c # K = success, Z = temporary error, D = permanent error - message: ['Hi. This is the qmail'], error: ['Remote host said:'], + message: ['Hi. This is the qmail'], + rhost: ['Giving up on ', 'Connected to ', 'remote host '], }.freeze - - ReSMTP = { + CommandSet = { # Error text regular expressions which defined in qmail-remote.c # qmail-remote.c:225| if (smtpcode() != 220) quit("ZConnected to "," but greeting failed"); - 'conn' => %r/(?:Error:)?Connected to [^ ]+ but greeting failed[.]/, + 'conn' => [' but greeting failed.'], # qmail-remote.c:231| if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected"); - 'ehlo' => %r/(?:Error:)?Connected to [^ ]+ but my name was rejected[.]/, + 'ehlo' => [' but my name was rejected.'], # qmail-remote.c:238| if (code >= 500) quit("DConnected to "," but sender was rejected"); # reason = rejected - 'mail' => %r/(?:Error:)?Connected to [^ ]+ but sender was rejected[.]/, + 'mail' => [' but sender was rejected.'], # qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n"); # qmail-remote.c:253| out("s"); outhost(); out(" does not like recipient.\n"); # reason = userunknown - 'rcpt' => %r/(?:Error:)?[^ ]+ does not like recipient[.]/, + 'rcpt' => [' does not like recipient.'], # qmail-remote.c:265| if (code >= 500) quit("D"," failed on DATA command"); # qmail-remote.c:266| if (code >= 400) quit("Z"," failed on DATA command"); # qmail-remote.c:271| if (code >= 500) quit("D"," failed after I sent the message"); # qmail-remote.c:272| if (code >= 400) quit("Z"," failed after I sent the message"); - 'data' => %r{(?: - (?:Error:)?[^ ]+[ ]failed[ ]on[ ]DATA[ ]command[.] - |(?:Error:)?[^ ]+[ ]failed[ ]after[ ]I[ ]sent[ ]the[ ]message[.] - ) - }x, + 'data' => [' failed on DATA command', ' failed after I sent the message'], }.freeze - # qmail-remote.c:261| if (!flagbother) quit("DGiving up on ",""); - ReHost = %r{(?: - Giving[ ]up[ ]on[ ]([^ ]+[0-9a-zA-Z])[.]?\z - |Connected[ ]to[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ] - |remote[ ]host[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ]said: - ) - }x # qmail-send.c:922| ... (&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem(); HasExpired = 'this message has been in the queue too long.' - ReIsOnHold = %r/\A[^ ]+ does not like recipient[.][ ]+.+this message has been in the queue too long[.]\z/ + OnHoldPair = [' does not like recipient.', 'this message has been in the queue too long.'].freeze FailOnLDAP = { # qmail-ldap-1.03-20040101.patch:19817 - 19866 'suspend' => ['Mailaddress is administrative?le?y disabled'], # 5.2.1 @@ -107,10 +96,14 @@ def inquire(mhead, mbody) # by qmail, see https://cr.yp.to/qmail.html # e.g.) Received: (qmail 12345 invoked for bounce); 29 Apr 2009 12:34:56 -0000 # Subject: failure notice - tryto = /\A[(]qmail[ ]+\d+[ ]+invoked[ ]+(?:for[ ]+bounce|from[ ]+network)[)]/ + tryto = [['(qmail ', 'invoked for bounce)'], ['(qmail ', 'invoked from ', 'network)']] match = 0 match += 1 if mhead['subject'] == 'failure notice' - match += 1 if mhead['received'].any? { |a| a =~ tryto } + mhead['received'].each do |e| + # Received: (qmail 2222 invoked for bounce);29 Apr 2017 23:34:45 +0900 + # Received: (qmail 2202 invoked from network); 29 Apr 2018 00:00:00 +0900 + match += 1 if tryto.any? { |a| Sisimai::String.aligned(e, a) } + end return nil unless match > 0 dscontents = [Sisimai::Lhost.DELIVERYSTATUS] @@ -137,14 +130,14 @@ def inquire(mhead, mbody) # Giving up on 192.0.2.153. v = dscontents[-1] - if cv = e.match(/\A(?:To[ ]*:)?[<](.+[@].+)[>]:[ ]*\z/) + if e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>', ':']) # : if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = Sisimai::Address.s3s4(e[e.index('<'), e.size]) recipients += 1 elsif dscontents.size == recipients @@ -155,22 +148,28 @@ def inquire(mhead, mbody) v['alterrors'] = e if e.start_with?(StartingOf[:error][0]) next if v['rhost'] - next unless cv = e.match(ReHost) - v['rhost'] = cv[1] + StartingOf[:rhost].each do |r| + # Find a remote host name + p1 = e.index(r); next unless p1 + cm = r.size + p2 = e.index(' ', p1 + cm + 1) || p2 = e.rindex('.') + + v['rhost'] = e[p1 + cm, p2 - p1 - cm] + break + end end end return nil unless recipients > 0 - require 'sisimai/smtp/command' dscontents.each do |e| e['agent'] = 'qmail' e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || '' unless e['command'] # Get the SMTP command name for the session - ReSMTP.each_key do |r| + CommandSet.each_key do |r| # Verify each regular expression of SMTP commands - next unless e['diagnosis'] =~ ReSMTP[r] + next unless CommandSet[r].any? { |a| e['diagnosis'].include?(a) } e['command'] = r.upcase break end @@ -191,7 +190,7 @@ def inquire(mhead, mbody) e['reason'] = 'blocked' else # Try to match with each error message in the table - if e['diagnosis'] =~ ReIsOnHold + if Sisimai::String.aligned(e['diagnosis'], OnHoldPair) # To decide the reason require pattern match with Sisimai::Reason::* modules e['reason'] = 'onhold' else diff --git a/lib/sisimai/lhost/receivingses.rb b/lib/sisimai/lhost/receivingses.rb index 8508c2e2..2dedfdb8 100644 --- a/lib/sisimai/lhost/receivingses.rb +++ b/lib/sisimai/lhost/receivingses.rb @@ -27,7 +27,6 @@ def inquire(mhead, mbody) # Feedback-ID: 1.us-west-2.HX6/J9OVlHTadQhEu1+wdF9DBj6n6Pa9sW5Y/0pSOi8=:AmazonSES return nil unless mhead['x-ses-outgoing'] - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field @@ -88,8 +87,8 @@ def inquire(mhead, mbody) else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e end end diff --git a/lib/sisimai/lhost/sendgrid.rb b/lib/sisimai/lhost/sendgrid.rb index 580a8227..51aff9fa 100644 --- a/lib/sisimai/lhost/sendgrid.rb +++ b/lib/sisimai/lhost/sendgrid.rb @@ -21,7 +21,6 @@ def inquire(mhead, mbody) return nil unless mhead['return-path'] == '' return nil unless mhead['subject'] == 'Undelivered Mail Returned to Sender' - require 'sisimai/rfc1894' require 'sisimai/smtp/command' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field @@ -57,8 +56,7 @@ def inquire(mhead, mbody) # Fallback code for empty value or invalid formatted value # - Status: (empty) # - Diagnostic-Code: 550 5.1.1 ... (No "diagnostic-type" sub field) - next unless cv = e.match(/\ADiagnostic-Code:[ ](.+)/) - v['diagnosis'] = cv[1] + v['diagnosis'] = e[e.index(':') + 2, e.size] if e.start_with?('Diagnostic-Code: ') next end @@ -84,10 +82,14 @@ def inquire(mhead, mbody) v['diagnosis'] = o[2] elsif o[-1] == 'date' # Arrival-Date: 2012-12-31 23-59-59 - next unless cv = e.match(/\AArrival-Date: (\d{4})[-](\d{2})[-](\d{2}) (\d{2})[-](\d{2})[-](\d{2})\z/) - o[1] << 'Thu, ' << cv[3] + ' ' - o[1] << Sisimai::DateTime.monthname(false)[cv[2].to_i - 1] - o[1] << ' ' << cv[1] + ' ' << [cv[4], cv[5], cv[6]].join(':') + next unless e.start_with?('Arrival-Date: ') + cf = e[e.index(': ') + 2, e.size].split(' '); next unless cf.size == 2 + cw = cf[0].split('-'); next unless cw.size == 3 + ce = cf[1].split('-'); next unless ce.size == 3 + + o[1] << 'Thu, ' << cw[2] + ' ' + o[1] << Sisimai::DateTime.monthname(false)[cw[1].to_i - 1] + o[1] << ' ' << cw[0] + ' ' << ce.join(':') o[1] << ' ' << Sisimai::DateTime.abbr2tz('CDT') else # Other DSN fields defined in RFC3464 @@ -102,14 +104,15 @@ def inquire(mhead, mbody) if cv = Sisimai::SMTP::Command.find(e) # in RCPT TO, in MAIL FROM, end of DATA thecommand = cv - elsif cv = e.match(/\ADiagnostic-Code:[ ]*(.+)\z/) + elsif e.start_with?('Diagnostic-Code: ') # Diagnostic-Code: 550 5.1.1 ... User Unknown - v['diagnosis'] = e + v['diagnosis'] = e[e.index(':') + 2, e.size] else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] ||= '' + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e end end @@ -119,10 +122,8 @@ def inquire(mhead, mbody) dscontents.each do |e| # Get the value of SMTP status code as a pseudo D.S.N. e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) - if cv = e['diagnosis'].match(/\b([45])\d\d[ \t]*/) - # 4xx or 5xx - e['status'] = cv[1] + '.0.0' - end + e['replycode'] = Sisimai::SMTP::Reply.find(e['diagnosis']) || '' + e['status'] = e['replycode'][0, 1] + '.0.0' if e['replycode'].size == 3 if e['status'] == '5.0.0' || e['status'] == '4.0.0' # Get the value of D.S.N. from the error message or the value of Diagnostic-Code header. diff --git a/lib/sisimai/lhost/sendmail.rb b/lib/sisimai/lhost/sendmail.rb index 7e5aa731..16be16e2 100644 --- a/lib/sisimai/lhost/sendmail.rb +++ b/lib/sisimai/lhost/sendmail.rb @@ -4,6 +4,9 @@ module Sisimai::Lhost module Sendmail class << self require 'sisimai/lhost' + require 'sisimai/smtp/reply' + require 'sisimai/smtp/status' + require 'sisimai/smtp/command' Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'].freeze @@ -26,14 +29,14 @@ class << self # @return [Hash] Bounce data list and message/rfc822 part # @return [Nil] it failed to parse or the arguments are missing def inquire(mhead, mbody) - return nil unless mhead['subject'] =~ /(?:see transcript for details\z|\AWarning: )/ return nil if mhead['x-aol-ip'] + match = nil + match ||= true if mhead['subject'].end_with?('see transcript for details') + match ||= true if mhead['subject'].start_with?('Warning: ') + return nil unless match - require 'sisimai/rfc1894' - require 'sisimai/smtp/command' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field - dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) bodyslices = emailparts[0].split("\n") @@ -109,9 +112,10 @@ def inquire(mhead, mbody) # >>> DATA thecommand = Sisimai::SMTP::Command.find(e) - elsif cv = e.match(/\A[<]{3}[ ]+(.+)\z/) + elsif e.start_with?('<<< ') # <<< Response - esmtpreply << cv[1] unless esmtpreply.index(cv[1]) + cv = e[4, e.size - 4] + esmtpreply << cv unless esmtpreply.index(cv) else # Detect SMTP session error or connection error next if sessionerr @@ -122,21 +126,23 @@ def inquire(mhead, mbody) next end - if cv = e.match(/\A[<](.+)[>][.]+ (.+)\z/) + if e.start_with?('<') && Sisimai::String.aligned(e, ['@', '>.', ' ']) # ... Deferred: Name server: example.co.jp.: host name lookup failure - anotherset['recipient'] = cv[1] - anotherset['diagnosis'] = cv[2] + anotherset['recipient'] = Sisimai::Address.s3s4(e[0, e.index('>')]) + anotherset['diagnosis'] = e[e.index(' ') + 1, e.size] else # ----- Transcript of session follows ----- # Message could not be delivered for too long # Message will be deleted from queue - next if e =~ /\A[ ]*[-]+/ - if cv = e.match(/\A[45]\d\d[ ]([45][.]\d[.]\d)[ ].+/) + cr = Sisimai::SMTP::Reply.find(e) || '' + cs = Sisimai::SMTP::Status.find(e) || '' + + if cr.size + cs.size > 7 # 550 5.1.2 ... Message # # DBI connect('dbname=...') # 554 5.3.0 unknown mailer error 255 - anotherset['status'] = cv[1] + anotherset['status'] = cs anotherset['diagnosis'] ||= '' anotherset['diagnosis'] << ' ' << e @@ -151,8 +157,8 @@ def inquire(mhead, mbody) else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e end end @@ -168,35 +174,41 @@ def inquire(mhead, mbody) if anotherset['diagnosis'] # Copy alternative error message e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].start_with?(' ') - e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].empty? e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].=~ /\A\d+\z/ + e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].empty? end - unless esmtpreply.empty? - # Replace the error message in "diagnosis" with the ESMTP Reply - r = esmtpreply.join(' ') - e['diagnosis'] = r if r.size > e['diagnosis'].size + + while true + # Replace or append the error message in "diagnosis" with the ESMTP Reply Code when the + # following conditions have matched + break if esmtpreply.empty? + break if recipients != 1 + + e['diagnosis'] = sprintf("%s %s", esmtpreply.join(' '), e['diagnosis']) + break end + e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) e['command'] ||= thecommand || Sisimai::SMTP::Command.find(e['diagnosis']) || '' if e['command'].empty? e['command'] = 'EHLO' unless esmtpreply.empty? end - if anotherset['status'] - # Check alternative status code - if e['status'].empty? || e['status'] !~ /\A[45][.]\d[.]\d{1,3}\z/ - # Override alternative status code - e['status'] = anotherset['status'] - end - end + while true + # Check alternative status code and override it + break unless anotherset.has_key?('status') + break unless anotherset['status'].size > 0 + break if Sisimai::SMTP::Status.test(e['status']) - unless e['recipient'] =~ /\A[^ ]+[@][^ ]+\z/ - # @example.jp, no local part - if cv = e['diagnosis'].match(/[<]([^ ]+[@][^ ]+)[>]/) - # Get email address from the value of Diagnostic-Code header - e['recipient'] = cv[1] - end + e['status'] = anotherset['status'] + break end + + # @example.jp, no local part + # # Get email address from the value of Diagnostic-Code field + next unless e['recipient'].start_with?('@') + cv = Sisimai::Address.find(e['diagnosis'], true) || [] + e['recipient'] = cv[0][:address] if cv.size > 0 end return { 'ds' => dscontents, 'rfc822' => emailparts[1] } @@ -205,3 +217,4 @@ def description; return 'V8Sendmail: /usr/sbin/sendmail'; end end end end + diff --git a/lib/sisimai/lhost/surfcontrol.rb b/lib/sisimai/lhost/surfcontrol.rb index 7dbee0fa..a25649c7 100644 --- a/lib/sisimai/lhost/surfcontrol.rb +++ b/lib/sisimai/lhost/surfcontrol.rb @@ -22,7 +22,6 @@ def inquire(mhead, mbody) return nil unless mhead['x-mailer'] return nil unless mhead['x-mailer'] == 'SurfControl E-mail Filter' - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) @@ -56,24 +55,27 @@ def inquire(mhead, mbody) # --- Message non-deliverable. v = dscontents[-1] - if cv = e.match(/\AAddressed To:[ ]*([^ ]+?[@][^ ]+?)\z/) + if e.start_with?('Addressed To:') && e.index('@') > 1 # Addressed To: kijitora@example.com if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = Sisimai::Address.s3s4(e[e.index(':') + 2, e.size]) recipients += 1 - elsif e =~ /\A(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[ ,]/ + elsif %w[Sun Mon Tue Wed Thu Fri Sat].any? { |a| e.start_with?(a) } # Thu 29 Apr 2010 23:34:45 +0900 v['date'] = e - elsif cv = e.match(/\A[^ ]+[@][^ ]+:[ ]*\[(\d+[.]\d+[.]\d+[.]\d)\],[ ]*(.+)\z/) + elsif Sisimai::String.aligned(e, ['@', ':', ' ', '[', '],', '...']) # kijitora@example.com: [192.0.2.5], 550 kijitora@example.com... No such user - v['rhost'] = cv[1] - v['diagnosis'] = cv[2] + p1 = e.index('[') + p2 = e.index('],', p1 + 1) + v['rhost'] = e[p1 + 1, p2 - p1 - 1] + v['diagnosis'] = Sisimai::String.sweep(e[p2 + 2, e.size]) + else # Fallback, parse RFC3464 headers. if f = Sisimai::RFC1894.match(e) @@ -85,8 +87,8 @@ def inquire(mhead, mbody) else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e end end diff --git a/lib/sisimai/lhost/v5sendmail.rb b/lib/sisimai/lhost/v5sendmail.rb index de6485cd..9f90d500 100644 --- a/lib/sisimai/lhost/v5sendmail.rb +++ b/lib/sisimai/lhost/v5sendmail.rb @@ -7,8 +7,7 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = [' ----- Unsent message follows -----', ' ----- No message was collected -----'].freeze - StartingOf = { message: ['----- Transcript of session follows -----'] } - MarkingsOf = { + StartingOf = { # Error text regular expressions which defined in src/savemail.c # savemail.c:485| (void) fflush(stdout); # savemail.c:486| p = queuename(e->e_parent, 'x'); @@ -25,7 +24,8 @@ class << self # savemail.c:497| while (fgets(buf, sizeof buf, xfile) != NULL) # savemail.c:498| putline(buf, fp, m); # savemail.c:499| (void) fclose(xfile); - error: %r/\A[.]+ while talking to .+[:]\z/, + error: [' while talking to '], + message: ['----- Transcript of session follows -----'], }.freeze # Parse bounce messages from Sendmail version 5 @@ -45,9 +45,9 @@ def inquire(mhead, mbody) bodyslices = emailparts[0].split("\n") readcursor = 0 # (Integer) Points the current cursor position recipients = 0 # (Integer) The number of 'Final-Recipient' header + anotherset = {} # (Hash) Another error information responding = [] # (Array) Responses from remote server commandset = [] # (Array) SMTP command which is sent to remote server - anotherset = {} # (Hash) Another error information errorindex = -1 # (Integer) v = nil @@ -70,15 +70,17 @@ def inquire(mhead, mbody) # 421 example.org (smtp)... Deferred: Connection timed out during user open with example.org v = dscontents[-1] - if cv = e.match(/\A\d{3}[ ]+[<]([^ ]+[@][^ ]+)[>][.]{3}[ ]*(.+)\z/) + if e.start_with?('5', '4') && Sisimai::String.aligned(e, [' <', '@', '>...']) # 550 ... User unknown if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] - v['diagnosis'] = cv[2] + p1 = e.index('<', 0) + p2 = e.index('>...') + v['recipient'] = e[p1 + 1, p2 - p1 - 1] + v['diagnosis'] = e[p2 + 5, e.size] # Concatenate the response of the server and error message v['diagnosis'] << ': ' << responding[recipients] if responding[recipients] @@ -88,38 +90,44 @@ def inquire(mhead, mbody) # >>> RCPT To: cv = Sisimai::SMTP::Command.find(e); commandset[recipients] = cv if cv - elsif cv = e.match(/\A[<]{3}[ ]+(.+)\z/) + elsif e.start_with?('<<< ') # <<< Response # <<< 501 ... no access from mail server [192.0.2.55] which is an open relay. # <<< 550 Requested User Mailbox not found. No such user here. - responding[recipients] = cv[1] + responding[recipients] = e[4, e.size] + else # Detect SMTP session error or connection error next if v['sessionerr'] - if e =~ MarkingsOf[:error] + if e.include?(StartingOf[:error][0]) # ----- Transcript of session follows ----- # ... while talking to mta.example.org.: v['sessionerr'] = true next end - if cv = e.match(/\A\d{3}[ ]+.+[.]{3}[ ]*(.+)\z/) + if e.start_with?('4', '5') && e.include?('... ') # 421 example.org (smtp)... Deferred: Connection timed out during user open with example.org - anotherset['diagnosis'] = cv[1] + anotherset['replycode'] = e[0, 3] + anotherset['diagnosis'] = e[e.index('... ') + 4, e.size] end end end - if recipients == 0 && cv = emailparts[1].match(/^To:[ ](.+)$/) + p1 = emailparts[1].index("\nTo: ") || -1 + p2 = emailparts[1].index("\n", p1 + 6) || -1 + if recipients == 0 && p1 > 0 # Get the recipient address from "To:" header at the original message - dscontents[0]['recipient'] = Sisimai::Address.s3s4(cv[1]) + dscontents[0]['recipient'] = Sisimai::Address.s3s4(emailparts[1][p1, p2 - p1 - 5]) recipients = 1 end return nil unless recipients > 0 dscontents.each do |e| errorindex += 1 + e.delete('sessionerr') + e['diagnosis'] ||= if anotherset['diagnosis'].to_s.size > 0 # Copy alternative error message anotherset['diagnosis'] @@ -128,16 +136,15 @@ def inquire(mhead, mbody) responding[errorindex] end e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) + e['replycode'] = Sisimai::SMTP::Reply.find(e['diagnosis']) || anotherset['replycode'] e['command'] = commandset[errorindex] || Sisimai::SMTP::Command.find(e['diagnosis']) || '' - unless e['recipient'] =~ /\A[^ ]+[@][^ ]+\z/ - # @example.jp, no local part - if cv = e['diagnosis'].match(/[<]([^ ]+[@][^ ]+)[>]/) - # Get email address from the value of Diagnostic-Code header - e['recipient'] = cv[1] - end - end - e.delete('sessionerr') + # @example.jp, no local part + # Get email address from the value of Diagnostic-Code header + next if e['recipient'].include?('@') + p1 = e['diagnosis'].index('<'); next unless p1 + p2 = e['diagnosis'].index('>'); next unless p2 + e['recipient'] = Sisimai::Address.s3s4(e[p1, p2 - p1]) end return { 'ds' => dscontents, 'rfc822' => emailparts[1] } diff --git a/lib/sisimai/lhost/verizon.rb b/lib/sisimai/lhost/verizon.rb index e7271060..40527cb4 100644 --- a/lib/sisimai/lhost/verizon.rb +++ b/lib/sisimai/lhost/verizon.rb @@ -18,7 +18,7 @@ def inquire(mhead, mbody) # :'subject' => %r/Undeliverable Message/, break unless mhead['received'].any? { |a| a.include?('.vtext.com (') } match = 1 if mhead['from'] == 'post_master@vtext.com' - match = 0 if mhead['from'] =~ /[<]?sysadmin[@].+[.]vzwpix[.]com[>]?\z/ + match = 0 if Sisimai::String.aligned(mhead['from'], ['sysadmin@', '.vzwpix.com']) break end return nil if match < 0 @@ -30,7 +30,6 @@ def inquire(mhead, mbody) recipients = 0 # (Integer) The number of 'Final-Recipient' header senderaddr = '' # (String) Sender address in the message body subjecttxt = '' # (String) Subject of the original message - markingsof = {} # (Hash) Delimiter patterns startingof = {} # (Hash) Delimiter strings messagesof = {} # (Hash) Error message patterns @@ -38,7 +37,7 @@ def inquire(mhead, mbody) if match == 1 # vtext.com - markingsof = { message: %r/\AError:[ ]/ } + markingsof = { message: ['Error: '] } messagesof = { # The attempted recipient address does not exist. 'userunknown' => ['550 - Requested action not taken: no such user here'], @@ -52,7 +51,7 @@ def inquire(mhead, mbody) # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part - readcursor |= Indicators[:deliverystatus] if e =~ markingsof[:message] + readcursor |= Indicators[:deliverystatus] if e.start_with?(markingsof[:message][0]) next end next if (readcursor & Indicators[:deliverystatus]) == 0 @@ -64,27 +63,27 @@ def inquire(mhead, mbody) # MAIL FROM: *******@hg.example.com # RCPT TO: *****@vtext.com v = dscontents[-1] - if cv = e.match(/\A[ ]+RCPT TO: (.*)\z/) + if e.start_with?(' RCPT TO: ') if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = e[11, e.size] recipients += 1 next - elsif cv = e.match(/\A[ ]+MAIL FROM:[ ](.+)\z/) + elsif e.start_with?(' MAIL FROM: ') # MAIL FROM: *******@hg.example.com - senderaddr = cv[1] if senderaddr.empty? + senderaddr = e[13, e.size] if senderaddr.empty? - elsif cv = e.match(/\A[ ]+Subject:[ ](.+)\z/) + elsif e.start_with?(' Subject: ') # Subject: - subjecttxt = cv[1] if subjecttxt.empty? + subjecttxt = e[11, e.size] if subjecttxt.empty? else # 550 - Requested action not taken: no such user here - v['diagnosis'] = e if e =~ /\A(\d{3})[ ][-][ ](.*)\z/ + v['diagnosis'] = e if e.include?(' - ') end end else @@ -112,35 +111,35 @@ def inquire(mhead, mbody) # Subject: test for bounce # Date: Wed, 20 Jun 2013 10:29:52 +0000 v = dscontents[-1] - if cv = e.match(/\ATo:[ ]+(.*)\z/) + if e.start_with?('To: ') if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = Sisimai::Address.s3s4(cv[1]) + v['recipient'] = Sisimai::Address.s3s4(e[4, e.size]) recipients += 1 next - elsif cv = e.match(/\AFrom:[ ](.+)\z/) + elsif e.start_with?('From: ') # From: kijitora - senderaddr = Sisimai::Address.s3s4(cv[1]) if senderaddr.empty? + senderaddr = Sisimai::Address.s3s4(e[4, e.size]) if senderaddr.empty? - elsif cv = e.match(/\ASubject:[ ](.+)\z/) + elsif e.start_with?('Subject: ') # Subject: - subjecttxt = cv[1] if subjecttxt.empty? + subjecttxt = e[9, e.size] if subjecttxt.empty? else # Message could not be delivered to mobile. # Error: No valid recipients for this MM - v['diagnosis'] = e if e =~ /\AError:[ ]+(.+)\z/ + v['diagnosis'] = Sisimai::String.sweep(e[7, e.size]) if e.start_with?('Error: ') end end end return nil unless recipients > 0 # Set the value of "MAIL FROM:" and "From:" - emailparts[1] << ('From: ' << senderaddr << "\n") unless emailparts[1] =~ /^From: / - emailparts[1] << ('Subject: ' << subjecttxt << "\n") unless emailparts[1] =~ /^Subject: / + emailparts[1] << ('From: ' << senderaddr << "\n") if emailparts[1].include?("\nFrom: ") == false + emailparts[1] << ('Subject: ' << subjecttxt << "\n") if emailparts[1].include?("\nSubject: ") == false dscontents.each do |e| e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) diff --git a/lib/sisimai/lhost/x1.rb b/lib/sisimai/lhost/x1.rb index a277852c..8e811037 100644 --- a/lib/sisimai/lhost/x1.rb +++ b/lib/sisimai/lhost/x1.rb @@ -7,7 +7,7 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['Received: from '].freeze - MarkingsOf = { message: %r/\AThe original message was received at (.+)\z/ }.freeze + MarkingsOf = { message: ['The original message was received at '] }.freeze # Parse bounce messages from Unknown MTA #1 # @param [Hash] mhead Message headers of a bounce email @@ -31,7 +31,7 @@ def inquire(mhead, mbody) # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + readcursor |= Indicators[:deliverystatus] if e.start_with?(MarkingsOf[:message][0]) next end next if (readcursor & Indicators[:deliverystatus]) == 0 @@ -45,20 +45,22 @@ def inquire(mhead, mbody) # kijitora@example.co.jp [User unknown] v = dscontents[-1] - if cv = e.match(/\A([^ ]+?[@][^ ]+?)[ ]+\[(.+)\]\z/) + if Sisimai::String.aligned(e, ['@', ' [', ']']) # kijitora@example.co.jp [User unknown] if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] - v['diagnosis'] = cv[2] + p1 = e.index(' ') + p2 = e.index(']') + v['recipient'] = e[0, p1] + v['diagnosis'] = e[p1 + 2, p2 - p1 - 2] recipients += 1 - elsif cv = e.match(MarkingsOf[:message]) + elsif e.start_with?(MarkingsOf[:message][0]) # The original message was received at Thu, 29 Apr 2010 23:34:45 +0900 (JST) - datestring = cv[1] + datestring = e[MarkingsOf[:message][0].size, e.size] end end return nil unless recipients > 0 diff --git a/lib/sisimai/lhost/x2.rb b/lib/sisimai/lhost/x2.rb index 9e882a7b..e5d8595e 100644 --- a/lib/sisimai/lhost/x2.rb +++ b/lib/sisimai/lhost/x2.rb @@ -15,8 +15,12 @@ class << self # @return [Hash] Bounce data list and message/rfc822 part # @return [Nil] it failed to parse or the arguments are missing def inquire(mhead, mbody) - return nil unless mhead['from'].include?('MAILER-DAEMON@') - return nil unless mhead['subject'] =~ %r/\A(?>Delivery failure|fail(?:ure|ed) delivery)/ + match = nil + match ||= 1 if mhead['from'].include?('MAILER-DAEMON@') + match ||= 1 if mhead['subject'].start_with?('Delivery failure') + match ||= 1 if mhead['subject'].start_with?('failure delivery') + match ||= 1 if mhead['subject'].start_with?('failed delivery') + return nil unless match dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) @@ -43,14 +47,14 @@ def inquire(mhead, mbody) # This user doesn't have a example.com account (kijitora@example.com) [0] v = dscontents[-1] - if cv = e.match(/\A[<]([^ ]+[@][^ ]+)[>]:\z/) + if e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>', ':']) # : if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = e[1, e.size - 3] recipients += 1 else # This user doesn't have a example.com account (kijitora@example.com) [0] diff --git a/lib/sisimai/lhost/x3.rb b/lib/sisimai/lhost/x3.rb index bc822c79..9d40824b 100644 --- a/lib/sisimai/lhost/x3.rb +++ b/lib/sisimai/lhost/x3.rb @@ -54,14 +54,14 @@ def inquire(mhead, mbody) # ============================================================================ v = dscontents[-1] - if cv = e.match(/\A[ ]+[*][ ]([^ ]+[@][^ ]+)\z/) + if e.include?(' * ') && e.include?('@') # * kijitora@example.com if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = e[e.index(' * ') + 3, e.size] recipients += 1 else # Detect error message @@ -70,13 +70,13 @@ def inquire(mhead, mbody) v['command'] = Sisimai::SMTP::Command.find(e) v['diagnosis'] = e - elsif cv = e.match(/\ARouting: (.+)/) + elsif e.start_with?('Routing: ') # Routing: Could not find a gateway for kijitora@example.co.jp - v['diagnosis'] = cv[1] + v['diagnosis'] = e[9, e.size] - elsif cv = e.match(/\ADiagnostic-Code: smtp; (.+)/) + elsif e.start_with?('Diagnostic-Code: smtp; ') # Diagnostic-Code: smtp; 552 5.2.2 Over quota - v['diagnosis'] = cv[1] + v['diagnosis'] = e[e.index(';') + 2, e.size] end end end diff --git a/lib/sisimai/lhost/x4.rb b/lib/sisimai/lhost/x4.rb index 3556b651..a5eb42ec 100644 --- a/lib/sisimai/lhost/x4.rb +++ b/lib/sisimai/lhost/x4.rb @@ -8,8 +8,7 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['--- Below this line is a copy of the message.', 'Original message follows.'].freeze - StartingOf = { error: ['Remote host said:'] }.freeze - MarkingsOf = { + StartingOf = { # qmail-remote.c:248| if (code >= 500) { # qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n"); # qmail-remote.c:265| if (code >= 500) quit("D"," failed on DATA command"); @@ -17,24 +16,39 @@ class << self # # Characters: K,Z,D in qmail-qmqpc.c, qmail-send.c, qmail-rspawn.c # K = success, Z = temporary error, D = permanent error - message: %r{\A(?> - He/Her[ ]is[ ]not[ ].+[ ]user - |Hi[.][ ].+[ ]unable[ ]to[ ]deliver[ ]your[ ]message[ ]to[ ] - the[ ]following[ ]addresses - |Su[ ]mensaje[ ]no[ ]pudo[ ]ser[ ]entregado - |This[ ]is[ ]the[ ](?: - machine[ ]generated[ ]message[ ]from[ ]mail[ ]service - |mail[ ]delivery[ ]agent[ ]at - ) - |Unable[ ]to[ ]deliver[ ]message[ ]to[ ]the[ ]following[ ]address - |Unfortunately,[ ]your[ ]mail[ ]was[ ]not[ ]delivered[ ]to[ ]the[ ]following[ ]address: - |Your[ ](?: - mail[ ]message[ ]to[ ]the[ ]following[ ]address - |message[ ]to[ ]the[ ]following[ ]addresses - ) - |We're[ ]sorry[.] - ) - }x, + error: ['Remote host said:'], + message: [ + 'He/Her is not ', + 'unable to deliver your message to the following addresses', + 'Su mensaje no pudo ser entregado', + 'This is the machine generated message from mail service', + 'This is the mail delivery agent at', + 'Unable to deliver message to the following address', + 'Unfortunately, your mail was not delivered to the following address:', + 'Your mail message to the following address', + 'Your message to the following addresses', + "We're sorry.", + ], + rhost: ['Giving up on ', 'Connected to ', 'remote host '], + }.freeze + CommandSet = { + # Error text regular expressions which defined in qmail-remote.c + # qmail-remote.c:225| if (smtpcode() != 220) quit("ZConnected to "," but greeting failed"); + 'conn' => [' but greeting failed.'], + # qmail-remote.c:231| if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected"); + 'ehlo' => [' but my name was rejected.'], + # qmail-remote.c:238| if (code >= 500) quit("DConnected to "," but sender was rejected"); + # reason = rejected + 'mail' => [' but sender was rejected.'], + # qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n"); + # qmail-remote.c:253| out("s"); outhost(); out(" does not like recipient.\n"); + # reason = userunknown + 'rcpt' => [' does not like recipient.'], + # qmail-remote.c:265| if (code >= 500) quit("D"," failed on DATA command"); + # qmail-remote.c:266| if (code >= 400) quit("Z"," failed on DATA command"); + # qmail-remote.c:271| if (code >= 500) quit("D"," failed after I sent the message"); + # qmail-remote.c:272| if (code >= 400) quit("Z"," failed after I sent the message"); + 'data' => [' failed on DATA command', ' failed after I sent the message'], }.freeze ReSMTP = { @@ -60,17 +74,10 @@ class << self ) }x, }.freeze - # qmail-remote.c:261| if (!flagbother) quit("DGiving up on ",""); - ReHost = %r{(?: - Giving[ ]up[ ]on[ ]([^ ]+[0-9a-zA-Z])[.]?\z - |Connected[ ]to[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ] - |remote[ ]host[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ]said: - ) - }x # qmail-send.c:922| ... (&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem(); HasExpired = 'this message has been in the queue too long.' - ReIsOnHold = %r/\A[^ ]+ does not like recipient[.][ ]+.+this message has been in the queue too long[.]\z/ + OnHoldPair = [' does not like recipient.', 'this message has been in the queue too long.'].freeze FailOnLDAP = { # qmail-ldap-1.03-20040101.patch:19817 - 19866 'suspend' => ['Mailaddress is administrative?le?y disabled'], # 5.2.1 @@ -125,13 +132,16 @@ def inquire(mhead, mbody) # https://cr.yp.to/qmail.html # e.g.) Received: (qmail 12345 invoked for bounce); 29 Apr 2009 12:34:56 -0000 # Subject: failure notice - tryto = %r/\A[(]qmail[ ]+\d+[ ]+invoked[ ]+for[ ]+bounce[)]/ + tryto = [['(qmail ', 'invoked for bounce)'], ['(qmail ', 'invoked from ', 'network)']].freeze match = 0 match += 1 if mhead['subject'].start_with?('failure notice', 'Permanent Delivery Failure') - match += 1 if mhead['received'].any? { |a| a =~ tryto } + mhead['received'].each do |e| + # Received: (qmail 2222 invoked for bounce);29 Apr 2017 23:34:45 +0900 + # Received: (qmail 2202 invoked from network); 29 Apr 2018 00:00:00 +0900 + match += 1 if tryto.any? { |a| Sisimai::String.aligned(e, a) } + end return nil unless match > 0 - require 'sisimai/smtp/command' dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailparts = Sisimai::RFC5322.part(mbody, Boundaries) bodyslices = emailparts[0].split("\n") @@ -144,7 +154,7 @@ def inquire(mhead, mbody) # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + readcursor |= Indicators[:deliverystatus] if StartingOf[:message].any? { |a| e.include?(a) } next end next if (readcursor & Indicators[:deliverystatus]) == 0 @@ -156,14 +166,14 @@ def inquire(mhead, mbody) # Giving up on 192.0.2.153. v = dscontents[-1] - if cv = e.match(/\A(?:To[ ]*:)?[<](.+[@].+)[>]:[ ]*\z/) + if e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>', ':']) # : if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = Sisimai::Address.s3s4(e[e.index('<'), e.size]) recipients += 1 elsif dscontents.size == recipients @@ -174,8 +184,15 @@ def inquire(mhead, mbody) v['alterrors'] = e if e.start_with?(StartingOf[:error][0]) next if v['rhost'] - next unless cv = e.match(ReHost) - v['rhost'] = cv[1] + StartingOf[:rhost].each do |r| + # Find a remote host name + p1 = e.index(r); next unless p1 + cm = r.size + p2 = e.index(' ', p1 + cm + 1) || p2 = e.rindex('.') + + v['rhost'] = e[p1 + cm, p2 - p1 - cm] + break + end end end return nil unless recipients > 0 @@ -185,9 +202,9 @@ def inquire(mhead, mbody) unless e['command'] # Get the SMTP command name for the session - ReSMTP.each_key do |r| + CommandSet.each_key do |r| # Verify each regular expression of SMTP commands - next unless e['diagnosis'] =~ ReSMTP[r] + next unless CommandSet[r].any? { |a| e['diagnosis'].include?(a) } e['command'] = r.upcase break end @@ -208,7 +225,7 @@ def inquire(mhead, mbody) e['reason'] = 'blocked' else # Try to match with each error message in the table - if e['diagnosis'] =~ ReIsOnHold + if Sisimai::String.aligned(e['diagnosis'], OnHoldPair) # To decide the reason require pattern match with Sisimai::Reason::* modules e['reason'] = 'onhold' else diff --git a/lib/sisimai/lhost/x5.rb b/lib/sisimai/lhost/x5.rb index 9d04f374..4b41c30a 100644 --- a/lib/sisimai/lhost/x5.rb +++ b/lib/sisimai/lhost/x5.rb @@ -35,7 +35,6 @@ def inquire(mhead, mbody) end return nil if match < 2 - require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE dscontents = [Sisimai::Lhost.DELIVERYSTATUS] readslices = [''] @@ -44,10 +43,10 @@ def inquire(mhead, mbody) v = nil # Pick the second message/rfc822 part because the format of email-x5-*.eml is nested structure - cutsbefore = mbody.split(Boundaries[0], 2) - cutsbefore[1].sub!(/\A.+?\n\n/s, '') - emailparts = Sisimai::RFC5322.part(cutsbefore[1], Boundaries) - bodyslices = emailparts[0].split("\n") + cutsbefore = mbody.split(Boundaries[0], 2) + cutsbefore[1] = cutsbefore[1][cutsbefore[1].index("\n\n") + 2, cutsbefore[1].size] + emailparts = Sisimai::RFC5322.part(cutsbefore[1], Boundaries) + bodyslices = emailparts[0].split("\n") while e = bodyslices.shift do # Read error messages and delivery status lines from the head of the email to the previous @@ -96,8 +95,8 @@ def inquire(mhead, mbody) else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e end end diff --git a/lib/sisimai/lhost/x6.rb b/lib/sisimai/lhost/x6.rb index e36b3310..50180d14 100644 --- a/lib/sisimai/lhost/x6.rb +++ b/lib/sisimai/lhost/x6.rb @@ -7,7 +7,7 @@ class << self Indicators = Sisimai::Lhost.INDICATORS Boundaries = ['The attachment contains the original mail headers'].freeze - MarkingsOf = { message: %r/\A\d+[ ]*error[(]s[)]:/ }.freeze + StartingOf = { message: ['We had trouble delivering your message. Full details follow:'] }.freeze # Parse bounce messages from Unknown MTA #6 # @param [Hash] mhead Message headers of a bounce email @@ -30,18 +30,26 @@ def inquire(mhead, mbody) # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part - readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] + readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0]) next end next if (readcursor & Indicators[:deliverystatus]) == 0 next if e.empty? + # We had trouble delivering your message. Full details follow: + # + # Subject: 'Nyaan' + # Date: 'Thu, 29 Apr 2012 23:34:45 +0000' + # # 1 error(s): # # SMTP Server rejected recipient # (Error following RCPT command). It responded as follows: [550 5.1.1 User unknown]v = dscontents[-1] - v = dscontents[-1] - if cv = e.match(/<([^ @]+[@][^ @]+)>/) || e.match(/errors:[ ]*([^ ]+[@][^ ]+)/) + v = dscontents[-1] + p1 = e.index('The following recipients returned permanent errors: ') + p2 = e.index('SMTP Server <') + + if p1 == 0 || p2 == 0 # SMTP Server rejected recipient # The following recipients returned permanent errors: neko@example.jp. if v['recipient'] @@ -49,7 +57,23 @@ def inquire(mhead, mbody) dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = Sisimai::Address.s3s4(cv[1]) + + if p1 == 0 + # The following recipients returned permanent errors: neko@example.jp. + p1 = e.index('errors: ') + p2 = e.index(' ', p1 + 8) + v['recipient'] = Sisimai::Address.s3s4(e[p1 + 8, p2 - p1 - 8]) + + elsif p2 == 0 + # SMTP Server rejected recipient + p1 = e.rindex('<') + p2 = e.rindex('>') + v['recipient'] = Sisimai::Address.s3s4(e[p1, p2 - p1]) + + else + next + end + v['diagnosis'] = e recipients += 1 end diff --git a/lib/sisimai/lhost/yahoo.rb b/lib/sisimai/lhost/yahoo.rb index 2190eb9c..0fd4cac4 100644 --- a/lib/sisimai/lhost/yahoo.rb +++ b/lib/sisimai/lhost/yahoo.rb @@ -46,16 +46,17 @@ def inquire(mhead, mbody) # Remote host said: 550 5.1.1 ... User Unknown [RCPT_TO] v = dscontents[-1] - if cv = e.match(/\A[<](.+[@].+)[>]:[ ]*\z/) + if e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>:']) # : if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + v['recipient'] = Sisimai::Address.s3s4(e[0, e.index('>:')]) v['diagnosis'] = '' recipients += 1 + else if e.start_with?('Remote host said:') # Remote host said: 550 5.1.1 ... User Unknown [RCPT_TO] @@ -89,7 +90,7 @@ def inquire(mhead, mbody) dscontents.each do |e| e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].gsub(/\\n/, ' ')) - e['command'] ||= 'RCPT' if e['diagnosis'] =~ /[<].+[@].+[>]/ + e['command'] ||= 'RCPT' if Sisimai::String.aligned(e['diagnosis'], ['<', '@', '>']) end return { 'ds' => dscontents, 'rfc822' => emailparts[1] } diff --git a/lib/sisimai/lhost/yandex.rb b/lib/sisimai/lhost/yandex.rb index abb9eb08..a2bd4882 100644 --- a/lib/sisimai/lhost/yandex.rb +++ b/lib/sisimai/lhost/yandex.rb @@ -25,8 +25,6 @@ def inquire(mhead, mbody) return nil unless mhead['x-yandex-uniq'] return nil unless mhead['from'] == 'mailer-daemon@yandex.ru' - require 'sisimai/rfc1894' - require 'sisimai/smtp/command' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field @@ -89,16 +87,14 @@ def inquire(mhead, mbody) # The line does not begin with a DSN field defined in RFC3464 if e.include?(' (in reply to ') || e.include?('command)') # 5.1.1 ... User Unknown (in reply to RCPT TO - cv = Sisimai::SMTP::Command.find(e); commandset << cv if cv + cv = Sisimai::SMTP::Command.find(e) + commandset << cv if cv - elsif cv = e.match(/([A-Z]{4})[ ]*.*command[)]\z/) - # to MAIL command) - commandset << cv[1] else # Continued line of the value of Diagnostic-Code field next unless readslices[-2].start_with?('Diagnostic-Code:') - next unless cv = e.match(/\A[ ]+(.+)\z/) - v['diagnosis'] << ' ' << cv[1] + next unless e.start_with?(' ') + v['diagnosis'] << ' ' << Sisimai::String.sweep(e) readslices[-1] = 'Diagnostic-Code: ' << e end end diff --git a/lib/sisimai/lhost/zoho.rb b/lib/sisimai/lhost/zoho.rb index 740ee55a..97b2edd0 100644 --- a/lib/sisimai/lhost/zoho.rb +++ b/lib/sisimai/lhost/zoho.rb @@ -53,15 +53,15 @@ def inquire(mhead, mbody) # shironeko@example.org Invalid Address, ERROR_CODE :550, ERROR_CODE :Requested action not taken: mailbox unavailable v = dscontents[-1] - if cv = e.match(/\A([^ ]+[@][^ ]+)[ ]+(.+)\z/) + if Sisimai::String.aligned(e, ['@', ' ', 'ERROR_CODE :']) # kijitora@example.co.jp Invalid Address, ERROR_CODE :550, ERROR_CODE :5.1.= if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] - v['diagnosis'] = cv[2] + v['recipient'] = e[0, e.index(' ')] + v['diagnosis'] = e[e.index(' ') + 1, e.size] if v['diagnosis'].end_with?('=') # Quoted printable @@ -70,7 +70,7 @@ def inquire(mhead, mbody) end recipients += 1 - elsif cv = e.match(/\A\[Status: .+[<]([^ ]+[@][^ ]+)[>],/) + elsif e.start_with?('[Status: ') # Expired # [Status: Error, Address: , ResponseCode 421, , Host not reachable.] if v['recipient'] @@ -78,7 +78,9 @@ def inquire(mhead, mbody) dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = cv[1] + p1 = e.index('<') + p2 = e.index('>', p1 + 2) + v['recipient'] = Sisimai::Address.s3s4(e[p1, p2 - p1]) v['diagnosis'] = e recipients += 1 else From 055eba89c86fa2437e8b0f70216ea4242f129b4e Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:33:04 +0900 Subject: [PATCH 17/37] Renamed: GoogleApps -> Google --- lib/sisimai/rhost/google.rb | 264 ++++++++++++++++++ set-of-emails/maildir/bsd/rhost-google-01.eml | 66 +++++ set-of-emails/maildir/bsd/rhost-google-02.eml | 88 ++++++ test/public/rhost-google.rb | 10 + 4 files changed, 428 insertions(+) create mode 100644 lib/sisimai/rhost/google.rb create mode 100644 set-of-emails/maildir/bsd/rhost-google-01.eml create mode 100644 set-of-emails/maildir/bsd/rhost-google-02.eml create mode 100644 test/public/rhost-google.rb diff --git a/lib/sisimai/rhost/google.rb b/lib/sisimai/rhost/google.rb new file mode 100644 index 00000000..5d69c74b --- /dev/null +++ b/lib/sisimai/rhost/google.rb @@ -0,0 +1,264 @@ +module Sisimai + module Rhost + # Sisimai::Rhost detects the bounce reason from the content of Sisimai::Fact object as an argument + # of get() method when the value of "rhost" of the object is "aspmx.l.google.com". This class is + # called only Sisimai::Fact class. + module Google + class << self + MessagesOf = { + 'authfailure' => [ + # - 550 5.7.26 Unauthenticated email from domain-name is not accepted due to domain's + # DMARC policy. Please contact the administrator of domain-name domain. If this was + # a legitimate mail please visit [Control unauthenticated mail from your domain] to + # learn about the DMARC initiative. If the messages are valid and aren't spam, con- + # tact the administrator of the receiving mail server to determine why your outgoing + # messages don't pass authentication checks. + ['550', '5.7.26', "is not accepted due to domain's dmarc policy"], + + # - 550 5.7.26 This message does not have authentication information or fails to pass + # authentication checks (SPF or DKIM). To best protect our users from spam, the mes- + # sage has been blocked. Please visit https://support.google.com/mail/answer/81126 + # for more information. + ['550', '5.7.1', 'fails to pass authentication checks'], + ['550', '5.7.26', 'fails to pass authentication checks'], + + # - 550 5.7.26 This message fails to pass SPF checks for an SPF record with a hard fail + # policy (-all). To best protect our users from spam and phishing, the message has + # been blocked. Please visit https://support.google.com/mail/answer/81126 for more + # information. + ['550', '5.7.26', 'this message fails to pass spf checks for an spf record with a hard fail'], + ], + 'badreputation' => [ + # - 550 5.7.1 Our system has detected that this message is likely suspicious due to the + # very low reputation of the sending IP address. To best protect our users from spam, + # the message has been blocked. + # Please visit https://support.google.com/mail/answer/188131 for more information. + ['550', '5.7.1', 'this message is likely suspicious due to the very low reputation of the sending ip address'], + ], + 'blocked' => [ + ['421', '4.7.0', 'ip not in whitelist for rcpt domain, closing connection.'], + + # - 421 4.7.0 Our system has detected an unusual rate of unsolicited mail originating + # from your IP address. To protect our users from spam, mail sent from your IP ad- + # dress has been temporarily blocked. + # For more information, visit https://support.google.com/mail/answer/81126 + ['421', '4.7.0', 'our system has detected an unusual rate of unsolicited mail originating from your ip address'], + + # - 421 4.7.0 Try again later, closing connection. This usually indicates a Denial of + # Service (DoS) for the SMTP relay at the HELO stage. + ['421', '4.7.0', 'try again later, closing connection.'], + + # - 501 5.5.4 HELO/EHLO argument is invalid. For more information, visit [HELO/EHLO e- + # mail error]. + ['501', '5.5.4', 'helo/ehlo argument is invalid'], + + # - 550 5.7.1 Our system has detected an unusual rate of unsolicited mail originating + # from your IP address. To protect our users from spam, mail sent from your IP ad- + # dress has been blocked. Review https://support.google.com/mail/answer/81126 + ['550', '5.7.1', 'our system has detected an unusual rate of unsolicited mail originating from your ip address'], + + # - 550 5.7.1 The IP you're using to send mail is not authorized to send email directly + # to our servers. Please use the SMTP relay at your service provider instead. For + # more information, visit https://support.google.com/mail/answer/10336 + ['550', '5.7.1', "the ip you're using to send mail is not authorized to send email directly to our servers"], + + # - 550 5.7.25 The IP address sending this message does not have a PTR record setup, or + # the corresponding forward DNS entry does not point to the sending IP. As a policy, + # Gmail does not accept messages from IPs with missing PTR records. + ['550', '5.7.25', 'the ip address sending this message does not have a ptr record setup'], + ], + 'contenterror' => [ + ['554', '5.6.0', 'mail message is malformed. Not accepted'], + ], + 'exceedlimit' => [ + # - 552 5.2.3 Your message exceeded Google's message size limits. For more information, + # visit https://support.google.com/mail/answer/6584 + ['552', '5.2.3', "your message exceeded Google's message size limits"], + ], + 'expired' => [ + ['451', '4.4.2', 'timeout - closing connection'], + ], + 'mailboxfull' => [ + # - 452 4.2.2 The email account that you tried to reach is over quota. Please direct + # the recipient to Clear Google Drive space & increase storage. + ['452', '4.2.2', 'the email account that you tried to reach is over quota'], + ['552', '5.2.2', 'the email account that you tried to reach is over quota'], + ['550', '5.7.1', 'email quota exceeded'], + ], + 'networkerror' => [ + ['554', '5.6.0', 'message exceeded 50 hops, this may indicate a mail loop'], + ], + 'norelaying' => [ + ['550', '5.7.0', 'mail relay denied'], + ], + 'policyviolation' => [ + ['550', '5.7.1', 'messages with multiple addresses in from: header are not accepted'], + + # - 550 5.7.1 The user or domain that you are sending to (or from) has a policy that + # prohibited the mail that you sent. Please contact your domain administrator for + # further details. + # For more information, visit https://support.google.com/a/answer/172179 + ['550', '5.7.1', 'the user or domain that you are sending to (or from) has a policy that prohibited'], + + # - 552 5.7.0 Our system detected an illegal attachment on your message. Please visit + # http://mail.google.com/support/bin/answer.py?answer=6590 to review our attachment + # guidelines. + ['552', '5.7.0', 'our system detected an illegal attachment on your message'], + + # - 552 5.7.0 This message was blocked because its content presents a potential securi- + # ty issue. Please visit https://support.google.com/mail/?p=BlockedMessage to review + # our message content and attachment content guidelines. + ['552', '5.7.0', 'this message was blocked because its content presents a potential security issue'], + ], + 'rejected' => [ + # - 550 5.7.0, Mail Sending denied. This error occurs if the sender account is disabled + # or not registered within your Google Workspace domain. + ['550', '5.7.0', 'mail sending denied'], + + ['550', '5.7.1', 'unauthenticated email is not accepted from this domain'], + ], + 'securityerror' => [ + ['421', '4.7.0', 'tls required for rcpt domain, closing connection'], + ['501', '5.5.2', 'cannot decode response'], # 2FA related error, maybe. + + # - 530 5.5.1 Authentication Required. For more information, visit + # https://support.google.com/accounts/troubleshooter/2402620 + ['530', '5.5.1', 'authentication required.'], + + # - 535 5.7.1 Application-specific password required. + # For more information, visit https://support.google.com/accounts/answer/185833 + ['535', '5.7.1', 'application-specific password required'], + + # - 535 5.7.1 Please log in with your web browser and then try again. For more infor- + # mation, visit https://support.google.com/mail/bin/accounts/answer/78754 + ['535', '5.7.1', 'please log in with your web browser and then try again'], + + # - 535 5.7.1 Username and Password not accepted. For more information, visit + # https://support.google.com/accounts/troubleshooter/2402620 + ['535', '5.7.1', 'username and password not accepted'], + + ['550', '5.7.1', 'invalid credentials for relay'], + ], + 'spamdetected' => [ + # - 550 5.7.1 Our system has detected that this message is likely unsolicited mail. To + # reduce the amount of spam sent to Gmail, this message has been blocked. + # For more information, visit https://support.google.com/mail/answer/188131 + ['550', '5.7.1', 'our system has detected that this message is likely unsolicited mail'], + ], + 'speeding' => [ + # - 450 4.2.1 The user you are trying to contact is receiving mail too quickly. Please + # resend your message at a later time. If the user is able to receive mail at that + # time, your message will be delivered. + # For more information, visit https://support.google.com/mail/answer/22839 + ['450', '4.2.1', 'is receiving mail too quickly'], + + # - 450 4.2.1 The user you are trying to contact is receiving mail at a rate that pre- + # vents additional messages from being delivered. Please resend your message at a + # later time. If the user is able to receive mail at that time, your message will be + # delivered. For more information, visit https://support.google.com/mail/answer/6592 + ['450', '4.2.1', 'is receiving mail at a rate that prevents additional messages from being delivered'], + ['550', '5.2.1', 'is receiving mail at a rate that prevents additional messages from being delivered'], + + # - 450 4.2.1 Peak SMTP relay limit exceeded for customer. This is a temporary error. + # For more information on SMTP relay limits, please contact your administrator or + # visit https://support.google.com/a/answer/6140680 + ['450', '4.2.1', 'peak smtp relay limit exceeded for customer'], + + # - 550 5.4.5 Daily SMTP relay limit exceeded for user. For more information on SMTP + # relay sending limits please contact your administrator or visit SMTP relay service + # error messages. + ['550', '5.4.5', 'daily smtp relay limit exceeded for user'], + ['550', '5.4.5', 'daily sending quota exceeded'], + + # - 550 5.7.1 Daily SMTP relay limit exceeded for customer. For more information on + # SMTP relay sending limits please contact your administrator or visit + # https://support.google.com/a/answer/6140680 + ['550', '5.7.1', 'daily smtp relay limit exceeded for customer'], + ], + 'suspend' => [ + ['550', '5.2.1', 'the email account that you tried to reach is disabled'], + ], + 'syntaxerror' => [ + ['451', '4.5.0', 'smtp protocol violation, visit rfc 2821'], + ['454', '4.5.0', 'smtp protocol violation, no commands allowed to pipeline after starttls'], + ['454', '5.5.1', 'starttls may not be repeated'], + ['502', '5.5.1', 'too many unrecognized commands, goodbye'], + ['502', '5.5.1', 'unimplemented command'], + ['502', '5.5.1', 'unrecognized command'], + ['503', '5.5.1', 'ehlo/helo first'], + ['503', '5.5.1', 'mail first'], + ['503', '5.5.1', 'rcpt first'], + ['503', '5.7.0', 'no identity changes permitted'], + ['504', '5.7.4', 'unrecognized authentication type'], + ['530', '5.7.0', 'must issue a starttls command first'], + ['535', '5.5.4', 'optional argument not permitted for that auth mode'], + ['554', '5.7.0', 'too many unauthenticated commands'], + ['555', '5.5.2', 'syntax error'], + ], + 'systemerror' => [ + ['421', '4.3.0', 'temporary system problem'], + ['421', '4.7.0', 'temporary system problem'], + ['421', '4.4.5', 'server busy'], + ['451', '4.3.0', 'mail server temporarily rejected message'], + ['454', '4.7.0', 'cannot authenticate due to temporary system problem'], + + # - 452 4.5.3 Domain policy size per transaction exceeded, please try this recipient in + # a separate transaction. This message means the email policy size (size of policies, + # number of policies, or both) for the recipient domain has been exceeded. + ['452', '4.5.3', 'domain policy size per transaction exceeded'], + ], + 'toomanyconn' => [ + ['451', '4.3.0', 'multiple destination domains per transaction is unsupported'], + + # - 452 4.5.3 Your message has too many recipients. For more information regarding + # Google's sending limits, visit https://support.google.com/mail/answer/6592 + ['452', '4.5.3', 'your message has too many recipients'], + ], + 'userunknown' => [ + # - 550 5.1.1 The email account that you tried to reach does not exist. Please try dou- + # ble-checking the recipient's email address for typos or unnecessary spaces. + # For more information, visit https://support.google.com/mail/answer/6596 + ['550', '5.1.1', 'the email account that you tried to reach does not exist'], + + # - 553 5.1.2 We weren't able to find the recipient domain. Please check for any spell- + # ing errors, and make sure you didn't enter any spaces, periods, or other punctua- + # tion after the recipient's email address. + ['553', '5.1.2', "we weren't able to find the recipient domain"], + ], + }.freeze + + # Detect bounce reason from Google Workspace + # @param [Sisimai::Fact] argvs Parsed email object + # @return [String] The bounce reason for Google Workspace + # @see https://support.google.com/a/answer/3726730?hl=en + def get(argvs) + return argvs['reason'] unless argvs['reason'].empty? + return '' if argvs['diagnosticcode'].empty? + return '' unless Sisimai::SMTP::Reply.test(argvs['replycode']) + return '' unless Sisimai::SMTP::Status.test(argvs['deliverystatus']) + + statuscode = argvs['deliverystatus'][2,6] + esmtpreply = argvs['replycode'][1,2] + esmtperror = argvs['diagnosticcode'].downcase + reasontext = '' + + MessagesOf.each_key do |e| + # Each key is a reason name + MessagesOf[e].each do |f| + # Try to match an SMTP reply code, a D.S.N, and an error message + next unless esmtperror.include?(f[2]) + next unless f[0].end_with?(esmtpreply) + next unless f[1].end_with?(statuscode) + reasontext = e + break + end + break unless reasontext.empty? + end + + return reasontext + end + + end + end + end +end diff --git a/set-of-emails/maildir/bsd/rhost-google-01.eml b/set-of-emails/maildir/bsd/rhost-google-01.eml new file mode 100644 index 00000000..473f4190 --- /dev/null +++ b/set-of-emails/maildir/bsd/rhost-google-01.eml @@ -0,0 +1,66 @@ +Received: from mail4.example.co.jp (1234c.example.com [192.0.2.1]) + by mx.example.jp (8.14.4/8.14.4) with ESMTP id r4B0078w00000 + for ; Mon, 11 May 2013 00:00:00 +0900 (JST) +Date: Mon, 11 May 2013 00:00:00 +0900 +From: Mail Delivery Subsystem +Message-Id: <201305110000000000000.r4B00000000000@mail4.example.co.jp> +To: postmaster@mail4.example.co.jp +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="r4B00000000000.0000000/mail4.example.co.jp" +Subject: Postmaster notify: see transcript for details + +This is a MIME-encapsulated message + +--r4B00000000000.0000000/mail4.example.co.jp + +The original message was received at Mon, 11 May 2013 00:00:00 +0900 +from localhost [127.0.0.1] +with id r4B0037M000000 + + ----- The following addresses had permanent fatal errors ----- + + (reason: 550 5.2.1 The email account that you tried to reach is disabled. g0000000000ggg.00) + + ----- Transcript of session follows ----- +... while talking to aspmx.l.google.com.: +>>> RCPT To: +<<< 550 5.2.1 The email account that you tried to reach is disabled. g0000000000ggg.00 +550 5.1.1 ... User unknown +>>> DATA +<<< 503 5.5.1 RCPT first. g0000000000ggg.00 + +--r4B00000000000.0000000/mail4.example.co.jp +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail4.example.co.jp +Received-From-MTA: DNS; localhost.example.com +Arrival-Date: Mon, 11 May 2013 00:00:00 +0900 + +Final-Recipient: RFC822; shironeko@example.ne.jp +Action: failed +Status: 5.2.1 +Remote-MTA: DNS; aspmx.l.google.com +Diagnostic-Code: SMTP; 550 5.2.1 The email account that you tried to reach is disabled. g0000000000ggg.00 +Last-Attempt-Date: Mon, 11 May 2013 00:00:00 +0900 + +--r4B00000000000.0000007/mail4.example.co.jp +Content-Type: text/rfc822-headers + +Return-Path: +Received: (from webmaster@localhost) + by mail4.example.co.jp (8.14.4/8.14.4/Submit) id r4B003v000000 + for shironeko@example.ne.jp; Mon, 11 May 2013 00:00:00 +0900 +Date: Mon, 11 May 2013 00:00:00 +0900 +Message-Id: <201305110000000000000.r4B003v000000@mail4.example.co.jp> +X-SENDTO: shironeko@example.ne.jp +Reply-To: mikeneko@example.co.jp +To: shironeko@example.ne.jp +From: kijitora@example.org +Subject: Bounce from GoogleApps MTA +MIME-Version: 1.0 +Content-Type: text/plain; charset="ISO-2022-JP" +Content-Transfer-Encoding: 7bit + +--r4B00000000000.0000007/mail4.example.co.jp-- + diff --git a/set-of-emails/maildir/bsd/rhost-google-02.eml b/set-of-emails/maildir/bsd/rhost-google-02.eml new file mode 100644 index 00000000..4f36ebf1 --- /dev/null +++ b/set-of-emails/maildir/bsd/rhost-google-02.eml @@ -0,0 +1,88 @@ +Return-Path: <> +X-Original-To: kijitora@example.com +Delivered-To: kijitora@example.com +Received: by mail.example.co.jp (Postfix) + id 49E71FA97E1D; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) +From: MAILER-DAEMON@mail.example.co.jp (Mail Delivery System) +Subject: Undelivered Mail Returned to Sender +To: kijitora@example.com +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="AA92C1B23442.1720293561/mail.example.co.jp" +Content-Transfer-Encoding: 7bit +Message-Id: <20180429233445.A7470DDE1C65@mail.example.co.jp> + +This is a MIME-encapsulated message. + +--AA92C1B23442.1528513261/mail.example.co.jp +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + +This is the mail system at host mail.example.co.jp. + +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 + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system + +: host aspmx.l.google.com[108.177.97.26] + said: 550-5.1.1 The email account that you tried to reach does not exist. + Please try 550-5.1.1 double-checking the recipient's email address for + typos or 550-5.1.1 unnecessary spaces. Learn more at 550 5.1.1 + https://support.google.com/mail/?p=NoSuchUser e22-n7GpZmsf093195.222 - + gsmtp (in reply to RCPT TO command) + +--AA92C1B23442.1528513261/mail.example.co.jp +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example.co.jp +X-Postfix-Queue-ID: AA92C1B23442 +X-Postfix-Sender: rfc822; kijitora@example.com +Arrival-Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) + +Final-Recipient: rfc822; neko-nyaan@example.org +Original-Recipient: rfc822;neko-nyaan@example.org +Action: failed +Status: 5.1.1 +Remote-MTA: dns; aspmx.l.google.com +Diagnostic-Code: smtp; 550-5.1.1 The email account that you tried to reach does + not exist. Please try 550-5.1.1 double-checking the recipient's email + address for typos or 550-5.1.1 unnecessary spaces. Learn more at 550 5.1.1 + https://support.google.com/mail/?p=NoSuchUser e22-n7GpZmsf093195.222 - + gsmtp + +--AA92C1B23442.1528513261/mail.example.co.jp +Content-Description: Undelivered Message +Content-Type: message/rfc822 +Content-Transfer-Encoding: 7bit + +Received: from localhost (localhost.localdomain [127.0.0.1]) + by mail.example.co.jp (Postfix) with ESMTP id AA92C1B23442 + for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Received: from mail.example.co.jp ([127.0.0.1]) + by localhost (neko1.example.co.jp [127.0.0.1]) (amavisd-new, port 24) + with ESMTP id F3BzCS-VimHsu-rf for ; + Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Received: from localhost (localhost.localdomain [127.0.0.1]) + by mail.example.co.jp (Postfix) with ESMTP id 567963C3A261 + for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +MIME-Version: 1.0 +Content-Type: text/plain; charset=ISO-2022-JP +Content-Transfer-Encoding: quoted-printable +From: "Neko" +To: neko-nyaan@example.org +Subject: Nyaan +Message-Id: <2018042233445.A95F8E533589@mail.example.co.jp> +Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) + +Nyaan + +--AA92C1B23442.1528513261/mail.example.co.jp-- diff --git a/test/public/rhost-google.rb b/test/public/rhost-google.rb new file mode 100644 index 00000000..c591a5b5 --- /dev/null +++ b/test/public/rhost-google.rb @@ -0,0 +1,10 @@ +module RhostEngineTest::Public + module Google + IsExpected = { + # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] + '01' => [['5.2.1', '550', 'suspend', false]], + '02' => [['5.1.1', '550', 'userunknown', true]], + } + end +end + From c7d4972a318bcb332c07862d54b9675345e2753c Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:35:07 +0900 Subject: [PATCH 18/37] Renamed: ExchangeOnline -> Microsoft --- .../rhost/{exchangeonline.rb => microsoft.rb} | 4 +- .../maildir/bsd/rhost-microsoft-01.eml | 73 +++++++ .../maildir/bsd/rhost-microsoft-02.eml | 89 ++++++++ .../maildir/bsd/rhost-microsoft-03.eml | 205 ++++++++++++++++++ ...t-exchangeonline.rb => rhost-microsoft.rb} | 2 +- 5 files changed, 370 insertions(+), 3 deletions(-) rename lib/sisimai/rhost/{exchangeonline.rb => microsoft.rb} (99%) create mode 100644 set-of-emails/maildir/bsd/rhost-microsoft-01.eml create mode 100644 set-of-emails/maildir/bsd/rhost-microsoft-02.eml create mode 100644 set-of-emails/maildir/bsd/rhost-microsoft-03.eml rename test/public/{rhost-exchangeonline.rb => rhost-microsoft.rb} (93%) diff --git a/lib/sisimai/rhost/exchangeonline.rb b/lib/sisimai/rhost/microsoft.rb similarity index 99% rename from lib/sisimai/rhost/exchangeonline.rb rename to lib/sisimai/rhost/microsoft.rb index 2ed4340c..e8669dc9 100644 --- a/lib/sisimai/rhost/exchangeonline.rb +++ b/lib/sisimai/rhost/microsoft.rb @@ -7,7 +7,7 @@ module Rhost # https://technet.microsoft.com/en-us/library/bb232118 # https://learn.microsoft.com/en-us/Exchange/mail-flow-best-practices/non-delivery-reports-in-exchange-online/non-delivery-reports-in-exchange-online # https://learn.microsoft.com/en-us/Exchange/mail-flow/non-delivery-reports-and-bounce-messages/non-delivery-reports-and-bounce-messages - module ExchangeOnline + module Microsoft class << self MessagesOf = { 'authfailure' => [ @@ -709,7 +709,7 @@ def get(argvs) return argvs['reason'] unless argvs['reason'].empty? return '' if argvs['diagnosticcode'].empty? return '' if argvs['deliverystatus'].empty? - return '' unless argvs['deliverystatus'] =~ /\A[245][.]\d[.]\d+\z/ + return '' unless Sisimai::SMTP::Status.test(argvs['deliverystatus']) statuscode = argvs['deliverystatus'] esmtperror = argvs['diagnosticcode'].downcase diff --git a/set-of-emails/maildir/bsd/rhost-microsoft-01.eml b/set-of-emails/maildir/bsd/rhost-microsoft-01.eml new file mode 100644 index 00000000..038996f8 --- /dev/null +++ b/set-of-emails/maildir/bsd/rhost-microsoft-01.eml @@ -0,0 +1,73 @@ +Return-Path: <> +Received: from nyaan.example.ed.jp (nyaan.example.ed.jp [198.51.100.222]) + by aneyakoji.neko.example.co.jp (V8/cf) with ESMTP id t3BB0AAA00000 + for ; Thu, 29 Apr 2015 20:00:00 +0900 +X-SenderID: Sendmail Sender-ID Filter v1.0.0 aneyakoji.neko.example.co.jp t3BB0AAA00000 +Authentication-Results: aneyakoji.neko.example.co.jp; sender-id=none header.from=MAILER-DAEMON@nyaan.example.ed.jp +Received: from localhost (localhost) + by nyaan.example.ed.jp (V8/cf) id t3BB0AAA11111; + Thu, 29 Apr 2015 20:00:00 +0900 +Date: Thu, 29 Apr 2015 20:00:00 +0900 +From: Mail Delivery Subsystem +Message-Id: <201505111100.t3BB0AAA11111@nyaan.example.ed.jp> +To: postmaster@nyaan.example.ed.jp +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="t3BB0AAA11111.1400000000/nyaan.example.ed.jp" +Subject: Postmaster notify: see transcript for details +Auto-Submitted: auto-generated (postmaster-notification) + +This is a MIME-encapsulated message + +--t3BB0AAA11111.1400000000/nyaan.example.ed.jp + +The original message was received at Thu, 29 Apr 2015 20:00:00 +0900 +from localhost [127.0.0.1] +with id t3BB0AAA22222 + + ----- The following addresses had permanent fatal errors ----- + + (reason: 550 5.7.606 Access denied, banned sending IP [198.51.100.222]. To request removal from this list plea...tions. For more information please go to http://go.microsoft.com/fwlink/?LinkID=526655 (AS16012609)) + + ----- Transcript of session follows ----- +... while talking to example.com.mail.protection.outlook.com.: +>>> DATA +<<< 550 5.7.606 Access denied, banned sending IP [198.51.100.222]. To request removal from this list please visit https://sender.office.com/ and follow the directions. For more information please go to http://go.microsoft.com/fwlink/?LinkID=526655 (AS16012609) +550 5.1.1 ... User unknown +<<< 503 5.5.2 Need rcpt command + +--t3BB0AAA11111.1400000000/nyaan.example.ed.jp +Content-Type: message/delivery-status + +Reporting-MTA: dns; nyaan.example.ed.jp +Received-From-MTA: DNS; localhost +Arrival-Date: Thu, 29 Apr 2015 20:00:00 +0900 + +Final-Recipient: RFC822; kijitora@example.com +Action: failed +Status: 5.7.606 +Remote-MTA: DNS; example.com.mail.protection.outlook.com +Diagnostic-Code: SMTP; 550 5.7.606 Access denied, banned sending IP [198.51.100.222]. To request removal from this list please visit https://sender.office.com/ and follow the directions. For more information please go to http://go.microsoft.com/fwlink/?LinkID=526655 (AS16012609) +Last-Attempt-Date: Thu, 29 Apr 2015 20:00:00 +0900 + +--t3BB0AAA11111.1400000000/nyaan.example.ed.jp +Content-Type: text/rfc822-headers + +Return-Path: +Received: from nyaan.example.ed.jp (localhost [127.0.0.1]) + by nyaan.example.ed.jp (V8/cf) with ESMTP id t3BB0AAA22222 + for ; Thu, 29 Apr 2015 20:00:00 +0900 +Received: (from root@localhost) + by nyaan.example.ed.jp (8.15.2/8.15.2/Submit) id t3BB0AAA23232; + Thu, 29 Apr 2015 20:00:00 +0900 +Date: Thu, 29 Apr 2015 20:00:00 +0900 +To: kijitora@example.com +Subject: Nyaan +From: Shironeko +Message-Id: <00000000000000000000000000000000000000000000000@example.net> +Content-Type: text/plain; charset="iso-2022-jp" +Content-Transfer-Encoding: 7bit +MIME-Version: 1.0 + +--t3BB0AAA11111.1400000000/nyaan.example.ed.jp-- + diff --git a/set-of-emails/maildir/bsd/rhost-microsoft-02.eml b/set-of-emails/maildir/bsd/rhost-microsoft-02.eml new file mode 100644 index 00000000..d30a2545 --- /dev/null +++ b/set-of-emails/maildir/bsd/rhost-microsoft-02.eml @@ -0,0 +1,89 @@ +Return-Path: +Received: from email110.example.com (unknown [192.0.2.222]) + by s151v.example.com (Bossmail) with ESMTP id FFF222EEE22 + for ; Thu, 29 Apr 2017 23:34:45 +0900 (JST) +Received: from email2.example.com ([192.0.2.22]) + by email110.example.com with ESMTP/TLS/DHE-RSA-AES256-SHA; 29 Apr 2017 23:34:45 +0900 +Received: by email2.example.com (Postfix) + id EEEEEEEEEE2; Thu, 29 Apr 2017 23:34:45 +0900 (JST) +Date: Thu, 29 Apr 2017 23:34:45 +0900 (JST) +From: MAILER-DAEMON@email2.example.com (Mail Delivery System) +Subject: Undelivered Mail Returned to Sender +To: sironeko@example.co.jp +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="2222FFFFEEEE.0000000000/email2.example.com" +Content-Transfer-Encoding: 8bit +Message-Id: <20170429233445.00000000000@email2.example.com> + +This is a MIME-encapsulated message. + +--2222FFFFEEEE.0000000000/email2.example.com +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + +This is the mail system at host email2.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 + +: host + example.org.mail.protection.outlook.com[198.51.100.2] said: 550 + 5.4.1 [kijitora@example.org]: Recipient address rejected: Access + denied [NEKOCHAN.cat-neko22.prod.protection.outlook.com] (in reply to + RCPT TO command) + +--2222FFFFEEEE.0000000000/email2.example.com +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; email2.example.com +X-Postfix-Queue-ID: 00000000000 +X-Postfix-Sender: rfc822; sironeko@example.co.jp +Arrival-Date: Thu, 29 Apr 2017 23:34:45 +0900 (JST) + +Final-Recipient: rfc822; kijitora@example.org +Original-Recipient: rfc822;kijitora@example.org +Action: failed +Status: 5.4.1 +Remote-MTA: dns; example.org.mail.protection.outlook.com +Diagnostic-Code: smtp; 550 5.4.1 [kijitora@example.org]: Recipient + address rejected: Access denied + [NEKOCHAN.cat-neko22.prod.protection.outlook.com] + +--2222FFFFEEEE.0000000000/email2.example.com +Content-Description: Undelivered Message +Content-Type: message/rfc822 +Content-Transfer-Encoding: 8bit + +Received: from smtp2.example.com (smtp2.example.com [203.0.113.2]) + by email2.example.com (Postfix) with ESMTP id eeeeeeeeeee + for ; Thu, 29 Apr 2017 23:34:45 +0900 (JST) +Received: from [192.0.2.2] (unknown [203.0.113.22]) + (Authenticated sender: sironeko@example.co.jp) + by relay1.example.com (nekomail) with ESMTP id fffffffff22 + for ; Thu, 29 Apr 2017 23:34:45 +0900 (JST) +Content-Type: multipart/mixed; boundary="===============222222220000222==" +MIME-Version: 1.0 +Subject: Nyaan +From: "Sironeko" +To: kijitora@example.org +Date: Thu, 29 Apr 2017 23:34:45 +0900 +Message-ID: <222222222000.2222.2222222222220000000@NEKO-Nyaan> + +--===============222222220000222== +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 + +Nyaan + +--===============222222220000222==-- + +--2222FFFFEEEE.0000000000/email2.example.com-- diff --git a/set-of-emails/maildir/bsd/rhost-microsoft-03.eml b/set-of-emails/maildir/bsd/rhost-microsoft-03.eml new file mode 100644 index 00000000..c80457fe --- /dev/null +++ b/set-of-emails/maildir/bsd/rhost-microsoft-03.eml @@ -0,0 +1,205 @@ +X-Apparently-To: sironeko@example.net; Thu, 29 Apr 2017 23:34:45 +0000 +Return-Path: <> +Received-SPF: none (domain of a2-222.smtp-out.amazonses.com does not designate permitted sender hosts) +X-Originating-IP: [192.0.2.222] +Received: from 127.0.0.1 (EHLO a2-222.smtp-out.amazonses.com) (192.0.2.222) + by relay2.mail.neko2.example.org with SMTP; Thu, 29 Apr 2017 23:34:45 +0000 +MIME-Version: 1.0 +From: MAILER-DAEMON@amazonses.com +To: sironeko@example.net +Date: Thu, 29 Apr 2017 23:34:45 +0000 +Content-Type: multipart/report; report-type=delivery-status; + boundary="22ffee22-2222-2222-2222-2222aaaabbbb" +X-MS-Exchange-Message-Is-Ndr: +Content-Language: en-US +Message-ID: <0000002222002222-22222222-2222-2222-ffff@email.amazonses.com> +Subject: Undeliverable: Nyaan +Auto-Submitted: auto-replied +X-MS-PublicTrafficType: Email +X-MS-TrafficTypeDiagnostic: 0000000000002: +X-MS-Office365-Filtering-Correlation-Id: 00002222-2222-2200-222200220000 +Received-SPF: None (protection.outlook.com: does not designate permitted + sender hosts) +Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=<>; +X-SES-Outgoing: 2017.04.29-192.0.2.222 +Content-Length: 1022 + +--22ffee22-2222-2222-2222-2222aaaabbbb +Content-Type: multipart/alternative; differences=Content-Type; + boundary="00002200-0022-2222-2002-222200220000" + +--00002200-0022-2222-2002-222200220000 +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: quoted-printable + +[http://products.office.com/en-us/CMSImages/Office365Logo_Orange.png?versio= +n=3Db8d100a9-0a8b-8e6a-88e1-ef488fee0470] +Your message to kijitora@example.com couldn't be delivered. + +kijitora wasn't found at example.com. + +2222222222220000-222. . . Office 365 kijitora +Action Required Recipient + + +Unknown To address + + +How to Fix It +The address may be misspelled or may not exist. Try one or more of the foll= +owing: + + * Send the message again following these steps: In Outlook, open this n= +on-delivery report (NDR) and choose Send Again from the Report ribbon. In O= +utlook on the web, select this NDR, then select the link "To send this mess= +age again, click here." Then delete and retype the entire recipient address= + If prompted with an Auto-Complete List suggestion don't select it. After = +typing the complete address, click Send. + * Contact the recipient (by phone, for example) to check that the addre= +ss exists and is correct. + * The recipient may have set up email forwarding to an incorrect addres= +s. Ask them to check that any forwarding they've set up is working correctl= +y. + * Clear the recipient Auto-Complete List in Outlook or Outlook on the w= +eb by following the steps in this article: Fix email delivery issues for er= +ror code 5.1.10 in Office 365, and then send the message again. Retype the entire recipient address b= +efore selecting Send. + + +If the problem continues, forward this message to your email admin. If you'= +re an email admin, refer to the More Info for Email Admins section below. + + +Was this helpful? Send feedback to Microsoft. +________________________________ + + +More Info for Email Admins +Status code: 550 5.1.10 + +This error occurs because the sender sent a message to an email address hos= +ted by Office 365 but the address is incorrect or doesn't exist at the dest= +ination domain. The error is reported by the recipient domain's email serve= +r, but most often it must be fixed by the person who sent the message. If t= +he steps in the How to Fix It section above don't fix the problem, and you'= +re the email admin for the recipient, try one or more of the following: + +The email address exists and is correct - Confirm that the recipient addres= +s exists, is correct, and is accepting messages. + +Synchronize your directories - If you have a hybrid environment and are usi= +ng directory synchronization make sure the recipient's email address is syn= +ced correctly in both Office 365 and in your on-premises directory. + +Errant forwarding rule - Check for forwarding rules that aren't behaving as= + expected. Forwarding can be set up by an admin via mail flow rules or mail= +box forwarding address settings, or by the recipient via the Inbox Rules fe= +ature. + +Recipient has a valid license - Make sure the recipient has an Office 365 l= +icense assigned to them. The recipient's email admin can use the Office 365= + admin center to assign a license (Users > Active Users > select the recipi= +ent > Assigned License > Edit). + +Mail flow settings and MX records are not correct - Misconfigured mail flow= + or MX record settings can cause this error. Check your Office 365 mail flo= +w settings to make sure your domain and any mail flow connectors are set up= + correctly. Also, work with your domain registrar to make sure the MX recor= +ds for your domain are configured correctly. + +For more information and additional tips to fix this issue, see Fix email d= +elivery issues for error code 5.1.10 in Office 365. + + +Original Message Details +Created Date: 4/29/2017 11:34:45 PM +Sender Address: 0000002222002222-22222222-2222-2222-ffff@amazonses.com +Recipient Address: kijitora@example.com +Subject: Nyaan + + +Error Details +Reported error: 550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipient not fo= +und by SMTP address lookup +DSN generated by: NEKO220022.namprd22.prod.outlook.com + + +Message Hops +HOP TIME (UTC) FROM TO WITH RELAY TIME +1 4/29/2017 +11:34:45 PM a2-22.smtp-out.amazonses.com NEKO22NYAAN0.mail.protecti= +on.outlook.com Microsoft SMTP Server (version=3DTLS1_0, cipher=3DTLS_= +ECDHE_RSA_WITH_AES_256_CBC_SHA_P384) 2 sec +2 4/29/2017 +11:34:45 PM NEKO22NYAAN0.eop-NAM03.prod.protection.outlook.com CAT= +NEKO0022.outlook.office365.com Microsoft SMTP Server (version=3DTLS1_= +2, cipher=3DTLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P256) 2 sec + +Original Message Headers + +Received: from NEKO22NYAAN22.namprd22.prod.outlook.com (198.51.100.2) by + NEKO220022.namprd22.prod.outlook.com (203.0.113.2) with Microsoft SMT= +P + Server (version=3DTLS1_2, cipher=3DTLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P= +256) id + 15.20.13.10; Thu, 29 Apr 2017 23:34:45 +0000 +Received: from NEKO22NYAAN0.eop-NAM03.prod.protection.outlook.com + (2222:2222:2222:2222::2222) by NEKO22NYAAN22.outlook.office365.com + (2022:2222:2222:2222::2222) with Microsoft SMTP Server (version=3DTLS1_2, + cipher=3DTLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P256) id 15.20.13.10 via + Frontend Transport; Thu, 29 Apr 2017 23:34:45 +0000 +Authentication-Results: spf=3Dpass (sender IP is 192.0.2.2) + smtp.mailfrom=3Damazonses.com; example.com; dkim=3Dpass (signature was veri= +fied) + header.d=3Damazonses.com;example.com; dmarc=3Dnone action=3Dnone + header.from=3Dexample.net; +Received-SPF: Pass (protection.outlook.com: domain of amazonses.com designa= +tes + 192.0.2.2 as permitted sender) receiver=3Dprotection.outlook.com; + client-ip=3D192.0.2.2; helo=3Da2-222.smtp-out.amazonses.com; +Received: from a2-222.smtp-out.amazonses.com (192.0.2.222) by + NEKO22NYAAN0.mail.protection.outlook.com (203.0.113.2) with Microsoft S= +MTP + Server (version=3DTLS1_0, cipher=3DTLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P384= +) id + 15.1.1222.22 via Frontend Transport; Thu, 29 Apr 2017 23:34:45 +0000 +Content-Type: text/html; charset=3Dutf-8 +MIME-Version: 1.0 +Date: Thu, 29 Apr 2017 23:34:45 +0000 +Content-Transfer-Encoding: quoted-printable +Subject: Nyaan +From: "Sironeko, Nyaan" +Message-ID: <2222222222220000-00000000-2222-2222-2222-000000002222-000000@e= +mail.amazonses.com> +To: kijitora@example.com +X-SES-Outgoing: 2017.04.29-192.0.2.2 +Return-Path: 2222222222221111-22222222-2222-0000-eeeeeeeeeeee@amazonses.com +X-MS-PublicTrafficType: Email +X-DkimResult-Test: Passed + +--00002200-0022-2222-2002-222200220000 +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: quoted-printable + +Nyaan + +--00002200-0022-2222-2002-222200220000-- + +--22ffee22-2222-2222-2222-2222aaaabbbb +Content-Type: message/delivery-status + +Reporting-MTA: dns;NEKO220022.namprd22.prod.outlook.com +Received-From-MTA: dns;a2-22.smtp-out.amazonses.com +Arrival-Date: Thu, 29 Apr 2017 23:34:45 +0000 + +Final-Recipient: rfc822;kijitora@example.com +Action: failed +Status: 5.1.10 +Diagnostic-Code: smtp;550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipient not found by SMTP address lookup + + +--22ffee22-2222-2222-2222-2222aaaabbbb-- + diff --git a/test/public/rhost-exchangeonline.rb b/test/public/rhost-microsoft.rb similarity index 93% rename from test/public/rhost-exchangeonline.rb rename to test/public/rhost-microsoft.rb index ee95a76f..296d691b 100644 --- a/test/public/rhost-exchangeonline.rb +++ b/test/public/rhost-microsoft.rb @@ -1,5 +1,5 @@ module RhostEngineTest::Public - module ExchangeOnline + module Microsoft IsExpected = { # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] '01' => [['5.7.606', '550', 'blocked', false]], From 2b0b65af7b7e62ec732380fe6e23ce5de87e2afa Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:35:52 +0900 Subject: [PATCH 19/37] Renamed: TencentQQ -> Tencent --- lib/sisimai/rhost/tencent.rb | 50 +++++++++++ .../maildir/bsd/rhost-tencent-01.eml | 84 +++++++++++++++++++ .../maildir/bsd/rhost-tencent-02.eml | 84 +++++++++++++++++++ .../maildir/bsd/rhost-tencent-03.eml | 81 ++++++++++++++++++ test/public/rhost-tencent.rb | 11 +++ 5 files changed, 310 insertions(+) create mode 100644 lib/sisimai/rhost/tencent.rb create mode 100644 set-of-emails/maildir/bsd/rhost-tencent-01.eml create mode 100644 set-of-emails/maildir/bsd/rhost-tencent-02.eml create mode 100644 set-of-emails/maildir/bsd/rhost-tencent-03.eml create mode 100644 test/public/rhost-tencent.rb diff --git a/lib/sisimai/rhost/tencent.rb b/lib/sisimai/rhost/tencent.rb new file mode 100644 index 00000000..9971881e --- /dev/null +++ b/lib/sisimai/rhost/tencent.rb @@ -0,0 +1,50 @@ +module Sisimai + module Rhost + # Sisimai::Rhost detects the bounce reason from the content of Sisimai::Fact object as an argument + # of get() method when the value of "rhost" of the object is "mx*.qq.com". This class is called + # only Sisimai::Fact class. + module Tencent + class << self + MessagesOf = { + # https://service.mail.qq.com/cgi-bin/help?id=20022 + 'dmarc check failed' => 'blocked', + 'spf check failed' => 'blocked', + 'suspected spam ip' => 'blocked', + 'mail is rejected by recipients' => 'filtered', + 'message too large' => 'mesgtoobig', + 'mail content denied' => 'spamdetected', + 'spam is embedded in the email' => 'spamdetected', + 'suspected spam' => 'spamdetected', + 'bad address syntax' => 'syntaxerror', + 'connection denied' => 'toomanyconn', + 'connection frequency limited' => 'toomanyconn', + 'domain frequency limited' => 'toomanyconn', + 'ip frequency limited' => 'toomanyconn', + 'sender frequency limited' => 'toomanyconn', + 'mailbox unavailable or access denied' => 'toomanyconn', + 'mailbox not found' => 'userunknown', + }.freeze + + # Detect bounce reason from Tencent QQ + # @param [Sisimai::Fact] argvs Parsed email object + # @return [String] The bounce reason at Tencent QQ + def get(argvs) + return argvs['reason'] unless argvs['reason'].empty? + + statusmesg = argvs['diagnosticcode'].downcase + reasontext = '' + + MessagesOf.each_key do |e| + # Try to match the error message with message patterns defined in $MessagesOf + next unless statusmesg.include?(e) + reasontext = MessagesOf[e] + break + end + return reasontext + end + + end + end + end +end + diff --git a/set-of-emails/maildir/bsd/rhost-tencent-01.eml b/set-of-emails/maildir/bsd/rhost-tencent-01.eml new file mode 100644 index 00000000..bd4befd8 --- /dev/null +++ b/set-of-emails/maildir/bsd/rhost-tencent-01.eml @@ -0,0 +1,84 @@ +Return-Path: <> +X-Original-To: kijitora-nyaan@em.example.com +Delivered-To: kijitora-nyaan@em.example.com +Received: by mail.example.net (Postfix) + id FA704A7BEB41; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) +From: MAILER-DAEMON@mail.example.net (Mail Delivery System) +Subject: Undelivered Mail Returned to Sender +To: kijitora-nyaan@em.example.com +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="886EC3C350D4.1965362333/mail.example.net" +Content-Transfer-Encoding: 7bit +Message-Id: <20180429233445.0D26F68F0B54@mail.example.net> + +This is a MIME-encapsulated message. + +--886EC3C350D4.1965362333/mail.example.net +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + +This is the mail system at host mail.example.net. + +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 + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system + +: host mx3.qq.com[103.7.30.40] said: 550 Ip frequency + limited [V2VkIEFwciAgMyAxNjozNjo1OSBKU1QgMjAxOQo=/RkNGMjlFQTVEQ0E Blocked + IP 192.0.2.22]. + http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000725 (in + reply to end of DATA command) + +--886EC3C350D4.1965362333/mail.example.net +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example.net +X-Postfix-Queue-ID: 92DCED2544DB +X-Postfix-Sender: rfc822; kijitora-nyaan@em.example.com +Arrival-Date: Thu, 6 Nov 2018 23:34:45 +0900 (JST) + +Final-Recipient: rfc822; nekochan@qq.example.cn +Original-Recipient: rfc822;nekochan@qq.example.cn +Action: failed +Status: 5.0.0 +Remote-MTA: dns; mx3.qq.com +Diagnostic-Code: smtp; 550 Ip frequency limited + [V2VkIEFwciAgMyAxNjozNjo1OSBKU1QgMjAxOQo=/RkNGMjlFQTVEQ0E Blocked IP + 192.0.2.22]. + http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000725 + +--886EC3C350D4.1965362333/mail.example.net +Content-Description: Undelivered Message +Content-Type: message/rfc822 +Content-Transfer-Encoding: 7bit + +Received: from localhost (localhost.localdomain [127.0.0.1]) + by mail.example.net (Postfix) with ESMTP id 8724A00AFDA3 + for ; Thu, 6 Nov 2018 23:34:45 +0900 (JST) +Received: from mail.example.net ([127.0.0.1]) + by localhost (neko1.example.net [127.0.0.1]) (amavisd-new, port 24) + with ESMTP id KCqxsygh6hzy6Mfw for ; + Thu, 6 Nov 2018 23:34:45 +0900 (JST) +Received: from localhost (localhost.localdomain [127.0.0.1]) + by mail.example.net (Postfix) with ESMTP id F6E14AF76696 + for ; Thu, 6 Nov 2018 23:34:45 +0900 (JST) +From: +To: +Subject: Nyaan +Content-type: text/plain; charset=ISO-2022-JP +Message-Id: <20180429233445.1BFAC0EE7C9F@mail.example.net> +Date: Thu, 6 Nov 2018 23:34:45 +0900 (JST) + +Nyaan + +--886EC3C350D4.1965362333/mail.example.net-- diff --git a/set-of-emails/maildir/bsd/rhost-tencent-02.eml b/set-of-emails/maildir/bsd/rhost-tencent-02.eml new file mode 100644 index 00000000..cc062ffe --- /dev/null +++ b/set-of-emails/maildir/bsd/rhost-tencent-02.eml @@ -0,0 +1,84 @@ +Return-Path: <> +X-Original-To: kijitora-neko@example.com +Delivered-To: kijitora-neko@example.com +Received: by mail.example.co.jp (Postfix) + id 30EDC7807231; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) +From: MAILER-DAEMON@mail.example.co.jp (Mail Delivery System) +Subject: Undelivered Mail Returned to Sender +To: kijitora-neko@example.com +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="9DD98D518C62.1219773614/mail.example.co.jp" +Content-Transfer-Encoding: 7bit +Message-Id: <20180429233445.FD0D225823D5@mail.example.co.jp> + +This is a MIME-encapsulated message. + +--9DD98D518C62.1219773614/mail.example.co.jp +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + +This is the mail system at host mail.example.co.jp. + +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 + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system + +: host mx3.qq.com[103.7.30.40] said: 550 Connection + frequency limited [Q1YzZ3kxdnRYYnpkbVZ0MAo+QTMzMTMzRjUxNzQ4Cg/MTI3MTY2MDge= + Blocked IP 192.0.2.22]. + http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000722 (in + reply to end of DATA command) + +--9DD98D518C62.1219773614/mail.example.co.jp +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example.co.jp +X-Postfix-Queue-ID: 9DD98D518C62 +X-Postfix-Sender: rfc822; kijitora-neko@example.com +Arrival-Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) + +Final-Recipient: rfc822; neko@qq.example.com +Original-Recipient: rfc822;neko@qq.example.com +Action: failed +Status: 5.0.0 +Remote-MTA: dns; mx3.qq.com +Diagnostic-Code: smtp; 550 Connection frequency limited + [Q1YzZ3kxdnRYYnpkbVZ0MAo+QTMzMTMzRjUxNzQ4Cg/MTI3MTY2MDge= Blocked IP + 192.0.2.22]. + http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000722 + +--9DD98D518C62.1219773614/mail.example.co.jp +Content-Description: Undelivered Message +Content-Type: message/rfc822 +Content-Transfer-Encoding: 7bit + +Received: from localhost (localhost.localdomain [127.0.0.1]) + by mail.example.co.jp (Postfix) with ESMTP id 90B28F34CE21 + for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Received: from mail.example.co.jp ([127.0.0.1]) + by localhost (neko1.example.co.jp [127.0.0.1]) (amavisd-new, port 24) + with ESMTP id 9WGfDFvM9hzgWRVW for ; + Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Received: from localhost (localhost.localdomain [127.0.0.1]) + by mail.example.co.jp (Postfix) with ESMTP id AB2A08507515 + for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +From: "Kijitora" +To: "Nyaan" +Subject: Nyaan +Content-type: text/plain; charset=ISO-2022-JP +Message-Id: <20180429233445.4278803910AC@mail.example.co.jp> +Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) + +Nyaan + +--9DD98D518C62.1219773614/mail.example.co.jp-- diff --git a/set-of-emails/maildir/bsd/rhost-tencent-03.eml b/set-of-emails/maildir/bsd/rhost-tencent-03.eml new file mode 100644 index 00000000..9ba176ea --- /dev/null +++ b/set-of-emails/maildir/bsd/rhost-tencent-03.eml @@ -0,0 +1,81 @@ +Return-Path: <> +X-Original-To: kijitora@example.net +Delivered-To: kijitora@example.net +Received: by mail.example.com (Postfix) + id 77DD768F06BA; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) +From: MAILER-DAEMON@mail.example.com (Mail Delivery System) +Subject: Undelivered Mail Returned to Sender +To: kijitora@example.net +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="815BDAE8A2BF.1707680793/mail.example.com" +Content-Transfer-Encoding: 7bit +Message-Id: <20180429233445.77DD768F06BA@mail.example.com> + +This is a MIME-encapsulated message. + +--815BDAE8A2BF.1707680793/mail.example.com +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + +This is the mail system at host mail.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 + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system + +: host mx3.qq.com[103.7.30.40] said: 550 DMARC check + failed. + http://service.mail.qq.com/cgi-bin/help?subtype=1&&no=1001508&&id=16. (in + reply to end of DATA command) + +--815BDAE8A2BF.1707680793/mail.example.com +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example.com +X-Postfix-Queue-ID: FC67BF0C6926 +X-Postfix-Sender: rfc822; kijitora@example.net +Arrival-Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) + +Final-Recipient: rfc822; neko@qq.example.cn +Original-Recipient: rfc822;neko@qq.example.cn +Action: failed +Status: 5.0.0 +Remote-MTA: dns; mx3.qq.com +Diagnostic-Code: smtp; 550 DMARC check failed. + http://service.mail.qq.com/cgi-bin/help?subtype=1&&no=1001508&&id=16. + +--815BDAE8A2BF.1707680793/mail.example.com +Content-Description: Undelivered Message +Content-Type: message/rfc822 +Content-Transfer-Encoding: 7bit + +Received: from localhost (localhost.localdomain [127.0.0.1]) + by mail.example.com (Postfix) with ESMTP id 815BDAE8A2BF + for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Received: from mail.example.com ([127.0.0.1]) + by localhost (neko1.example.com [127.0.0.1]) (amavisd-new, port 24) + with ESMTP id FpR1e6-NIX4NY-bn for ; + Thu, 29 Apr 2018 23:34:45 +0900 (JST) +Received: from localhost (localhost.localdomain [127.0.0.1]) + by mail.example.com (Postfix) with ESMTP id 663370D36901 + for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) +From: "Kijitora" +To: neko@qq.example.cn +Subject: Nyaan +Content-type: text/plain; charset=ISO-2022-JP +Message-Id: <20180429233445.939E1DDF19E1@mail.example.com> +Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) + +Nyaan + +--815BDAE8A2BF.1707680793/mail.example.com-- diff --git a/test/public/rhost-tencent.rb b/test/public/rhost-tencent.rb new file mode 100644 index 00000000..d0e2441a --- /dev/null +++ b/test/public/rhost-tencent.rb @@ -0,0 +1,11 @@ +module RhostEngineTest::Public + module Tencent + IsExpected = { + # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] + '01' => [['5.0.0', '550', 'toomanyconn', false]], + '02' => [['5.0.0', '550', 'toomanyconn', false]], + '03' => [['5.0.0', '550', 'blocked', false]], + } + end +end + From 60d4c33fc8b04b9de8ecf6fbf0e023244b880731 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:37:02 +0900 Subject: [PATCH 20/37] Reduce regular expressions --- lib/sisimai/smtp/reply.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/sisimai/smtp/reply.rb b/lib/sisimai/smtp/reply.rb index 1415a18f..5bebca69 100644 --- a/lib/sisimai/smtp/reply.rb +++ b/lib/sisimai/smtp/reply.rb @@ -145,8 +145,8 @@ def test(argv1 = '') # @param [String] argv2 Status code like 5.1.1 or 2 or 4 or 5 # @return [String] SMTP Reply Code # [Nil] The first argument did not include SMTP Reply Code value - def find(argv1 = '', argv2 = 'x') - return nil if argv1.to_s.size < 4 + def find(argv1 = '', argv2 = '0') + return nil if argv1.to_s.size < 3 return nil if argv1.upcase.include?('X-UNIX') statuscode = argv2[0, 1] @@ -155,7 +155,7 @@ def find(argv1 = '', argv2 = 'x') else [*CodeOfSMTP['5'], *CodeOfSMTP['4'], *CodeOfSMTP['2']] end - esmtperror = ' ' + argv1 + esmtperror = ' ' + argv1 + ' ' esmtpreply = '' # SMTP Reply Code replyindex = 0 # A position of SMTP reply code found by the index() formerchar = 0 # a character that is one character before the SMTP reply code @@ -165,7 +165,7 @@ def find(argv1 = '', argv2 = 'x') # Try to find an SMTP Reply Code from the given string replyindex = esmtperror.index(e); next unless replyindex formerchar = esmtperror[replyindex - 1, 1].ord || 0 - lattercahr = esmtperror[replyindex + 3, 1].ord || 0 + latterchar = esmtperror[replyindex + 3, 1].ord || 0 next if formerchar > 45 && formerchar < 58 next if latterchar > 45 && latterchar < 58 @@ -173,6 +173,7 @@ def find(argv1 = '', argv2 = 'x') break end + return nil if esmtpreply.empty? return esmtpreply end From 1598c15251bbe4c5135b676edcfafa4fc0dfa68f Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:37:21 +0900 Subject: [PATCH 21/37] Reduce regular expressions --- lib/sisimai/smtp/command.rb | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/sisimai/smtp/command.rb b/lib/sisimai/smtp/command.rb index abb186e9..449bb9bd 100644 --- a/lib/sisimai/smtp/command.rb +++ b/lib/sisimai/smtp/command.rb @@ -5,7 +5,7 @@ module Command class << self Detectable = [ 'HELO', 'EHLO', 'STARTTLS', 'AUTH PLAIN', 'AUTH LOGIN', 'AUTH CRAM-', 'AUTH DIGEST-', - 'MAIL F', 'RCPT', 'RCPT T', 'DATA' + 'MAIL F', 'RCPT', 'RCPT T', 'DATA', 'QUIT', 'XFORWARD', ].freeze # Check that an SMTP command in the argument is valid or not @@ -15,7 +15,11 @@ class << self def test(argv0 = '') return nil if argv0.empty? return nil if argv0.size < 4 - return true if %w[HELO EHLO MAIL RCPT DATA QUIT AUTH STARTTLS].any? { |a| argv0.include?(a) } + + comm0 = %w[HELO EHLO MAIL RCPT DATA QUIT RSET NOOP VRFY ETRN EXPN HELP] + comm1 = %w[AUTH STARTTLS XFORWARD] + return true if comm0.any? { |a| argv0.include?(a) } + return true if comm1.any? { |a| argv0.include?(a) } return true if argv0.include?('CONN') # CONN is a pseudo SMTP command used only in Sisimai return false end @@ -29,18 +33,20 @@ def find(argv0 = '') return nil unless Sisimai::SMTP::Command.test(argv0) stringsize = argv0.size + commandmap = { 'STAR' => 'STARTTLS', 'XFOR' => 'XFORWARD' } commandset = [] previouspp = 0 Detectable.each do |e| # Find an SMTP command from the given string - p = argv0.index(e, previouspp) - next unless p - next if p + 4 > stringsize - previouspp = p - v = argv0[p, 4] - next if commandset.include?(v) - commandset << v + p0 = argv0.index(e, previouspp) + next unless p0 + next if p0 + 4 > stringsize + previouspp = p0 + + cv = argv0[p0, 4]; next if commandset.include?(cv) + cv = commandmap[cv] if commandmap.has_key?(cv) + commandset << cv end return nil if commandset.empty? From b5322aa619d16c8a84e985b01abd30c156e39daa Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:38:15 +0900 Subject: [PATCH 22/37] Reduce regualr expressions, Implement Sisimai::SMTP::Status.prefer() method --- lib/sisimai/smtp/status.rb | 167 +++++++++++++++++++++++++++++--- test/public/smtp-status-test.rb | 19 +++- 2 files changed, 171 insertions(+), 15 deletions(-) diff --git a/lib/sisimai/smtp/status.rb b/lib/sisimai/smtp/status.rb index a2adf178..bc79f877 100644 --- a/lib/sisimai/smtp/status.rb +++ b/lib/sisimai/smtp/status.rb @@ -707,26 +707,165 @@ def test(argv1 = '') # Get a DSN code value from given string including DSN # @param [String] argv1 String including DSN - # @return [String, Nil] DSN or Nil if the first agument did not include DSN - def find(argv1 = nil) - return nil unless argv1 - return nil if argv1.empty? + # @param [String] argv2 An SMTP Reply Code or 2 or 4 or 5 + # @return [String, Nil] An SMTP Status Code + def find(argv1 = nil, argv2 = '0') + return nil if argv1.to_s.empty? + return nil if argv1.size < 7 + + givenclass = argv2[0, 1] + eestatuses = if givenclass == '2' || givenclass == '4' || givenclass == '5' + [givenclass + '.'] + else + ['5.', '4.', '2.'] + end + esmtperror = ' ' + argv1 + ' ' + lookingfor = [] + + Sisimai::String.ipv4(esmtperror).each do |e| + # Rewrite an IPv4 address in the given string(argv1) with '***.***.***.***' + p0 = esmtperror.index(e) || next + esmtperror[p0, e.size] = '***.***.***.***' + end - found = nil - CodePatterns.each do |e| - # Get the value of D.S.N. in the text - next unless r = argv1.match(e) - found = r[1] + eestatuses.each do |e| + # Count the number of "5.", "4.", and "2." in the error message + p0 = 0; p1 = 0 + while p0 + # Find all of the "5." and "4." string and store its postion + p0 = esmtperror.index(e, p1) || break + lookingfor << [p0, e] + p1 = p0 + 5 + end + end + return nil if lookingfor.size == 0 + + statuscode = [] # List of SMTP Status Code, Keep the order of appearances + anotherone = '' # Alternative code + readbuffer = '' + characters = [] # Characters around the status code found by index() + indexofees = nil # A position of SMTP status code found by the index() + + lookingfor.sort_by(&:first).each do |e| + # Try to find an SMTP Status Code from the given string + indexofees = esmtperror.index(e[1], e[0]); next unless indexofees + characters = [esmtperror[indexofees - 1, 1].ord] # [0] The previous character of the status + [2, 3].each do |i| + # [1] The value of the "Subject", "5.[7].261" + # [2] "." chacater, a separator of the Subject and the Detail + if indexofees + 1 + i > esmtperror.size + characters << 0 + else + characters << esmtperror[indexofees + i, 1].ord + end + end - if argv1 =~ /\b(?:#{found}[.]\d{1,3}|\d{1,3}[.]#{found})\b/ - # Clear and skip if the value is an IPv4 address - found = nil + next if characters[0] > 45 && characters[0] < 58 # Previous character is a number + next if characters[0] == 86 || characters[0] == 118 # Avoid a version number("V" or "v") + next if characters[1] < 48 || characters[1] > 55 # The value of the subject is not a number(0-7) + next if characters[2] != 46 # It is not a "." character: a separator + readbuffer = e[1] + characters[1].chr + '.' + + [4, 5, 6, 7].each do |i| + # [3] The 1st digit of the detail + # [4] The 2nd digit of the detail + # [5] The 3rd digit of the detail + # [6] The next character + if indexofees + 1 + i > esmtperror.size + characters << 0 + else + characters << esmtperror[indexofees + i, 1].ord + end + end + + next if characters[3] < 48 || characters[3] > 57 # The 1st digit of the detail is not a number + readbuffer << characters[3].chr + + if readbuffer.index('.0.0') || readbuffer == '4.4.7' + # Find another status code except *.0.0, 4.4.7 + anotherone = readbuffer next end - break + + if characters[4] < 48 || characters[4] > 57 + # The 2nd digit of the detail is not a number + statuscode << readbuffer + next + end + readbuffer << characters[4].chr # The 2nd digit of the detail is a number + + if characters[5] < 48 || characters[5] > 57 + # The 3rd digit of the detail is not a number + statuscode << readbuffer + next + end + readbuffer << characters[5].chr # The 3rd digit of the detail is a number + + next if characters[6] > 47 && characters[6] < 58 + statuscode << readbuffer + end + + statuscode << anotherone if anotherone.size > 0 + return nil if statuscode.size == 0 + return statuscode.shift + end + + # Return the preferred value selected from the arguments + # @param [String] argv0 The value of Status: + # @param [String] argv1 The delivery status picked from the error message + # @param [String] argv2 The value of An SMTP Reply Code + # @return [String] The preferred value + # @since v5.0.0 + def prefer(argv0 = nil, argv1 = nil, argv2 = nil) + return argv1 unless argv0; return argv1 unless argv0.size > 4 + return argv0 unless argv1; return argv0 unless argv1.size > 4 + + statuscode = argv0 + codeinmesg = argv1 + esmtpreply = argv2 || '000' + the1stchar = { + 'field' => statuscode[0, 1].to_i, + 'error' => codeinmesg[0, 1].to_i, + 'reply' => esmtpreply.to_s[0, 1].to_i, + } + + if the1stchar['reply'] > 0 && the1stchar['field'] != the1stchar['error'] + # There is the 3rd argument (an SMTP Reply Code) + # Returns the value of $argv0 or $argv1 which begins with the 1st character of argv2 + return statuscode if the1stchar['reply'] == the1stchar['field'] + return codeinmesg if the1stchar['reply'] == the1stchar['error'] + end + return statuscode if statuscode == codeinmesg + + zeroindex1 = { 'field' => statuscode.index('.0') || -1, 'error' => codeinmesg.index('.0') || -1 } + zeroindex2 = { 'field' => statuscode.index('.0.0') || -1, 'error' => codeinmesg.index('.0.0') || -1 } + + if zeroindex2['field'] > 0 + # "Status:" field is "X.0.0" + return codeinmesg if zeroindex2['error'] < 0 + return statuscode + end + + if zeroindex1['field'] > 0 + # "Status:" field is "X.Y.0" or "X.0.Z" + return codeinmesg if zeroindex1['error'] < 0 + end + + return statuscode if zeroindex2['error'] > 0 # An SMTP status code is "X.0.0" + return codeinmesg if statuscode == '4.4.7' # "4.4.7" is an ambiguous code + return codeinmesg if statuscode.start_with?('5.3.') # "5.3.Z" is an error of a system + + if statuscode == '5.1.1' + # "5.1.1" is a code of "userunknown" + return statuscode if zeroindex1['error'] > 0 + return codeinmesg + + elsif statuscode == '5.1.3' + # "5.1.3" + return codeinmesg if codeinmesg.start_with?('5.7.') end - return found + return statuscode end end diff --git a/test/public/smtp-status-test.rb b/test/public/smtp-status-test.rb index 9f999b8e..942449cd 100644 --- a/test/public/smtp-status-test.rb +++ b/test/public/smtp-status-test.rb @@ -93,7 +93,24 @@ def test_find ce = assert_raises ArgumentError do Sisimai::SMTP::Status.find() - Sisimai::SMTP::Status.find(nil, nil) + Sisimai::SMTP::Status.find(nil, nil, nil) + end + assert_nil Sisimai::SMTP::Status.find('') + end + + def test_prefer + assert_nil Sisimai::SMTP::Status.prefer(nil) + assert_equal '5.2.2', Sisimai::SMTP::Status.prefer('5.2.2', '') + assert_equal '5.3.5', Sisimai::SMTP::Status.prefer('', '5.3.5') + assert_equal '5.1.1', Sisimai::SMTP::Status.prefer('5.0.0', '5.1.1') + assert_equal '5.2.1', Sisimai::SMTP::Status.prefer('5.2.0', '5.2.1') + assert_equal '4.2.2', Sisimai::SMTP::Status.prefer('4.4.7', '4.2.2') + assert_equal '5.7.8', Sisimai::SMTP::Status.prefer('5.7.8', '4.4.0', 550) + assert_equal '4.2.1', Sisimai::SMTP::Status.prefer('4.2.1', '5.7.0', 421) + + ce = assert_raises ArgumentError do + Sisimai::SMTP::Status.prefer() + Sisimai::SMTP::Status.prefer(nil, nil, nil, nil) end assert_nil Sisimai::SMTP::Status.find('') end From e57339098a981ddaecd8d8ef5621da40ceb79abd Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:38:49 +0900 Subject: [PATCH 23/37] Reduce regular expressions --- lib/sisimai/smtp/transcript.rb | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/sisimai/smtp/transcript.rb b/lib/sisimai/smtp/transcript.rb index 52f7364f..139fabd5 100644 --- a/lib/sisimai/smtp/transcript.rb +++ b/lib/sisimai/smtp/transcript.rb @@ -3,6 +3,9 @@ module SMTP # Sisimai::SMTP::Transcript is a parser for transcript logs of SMTP session module Transcript class << self + require 'sisimai/smtp/reply' + require 'sisimai/smtp/status' + # Parse a transcript of an SMTP session and makes structured data # @param [String] argv0 A transcript text MTA returned # @param [String] argv1 A label string of a SMTP client @@ -34,17 +37,23 @@ def rise(argv0 = '', argv1 = '>>>', argv2 = '<<<') parameters = '' # Command parameters of MAIL, RCPT cursession = nil # Current session for $esmtp - if argv0.index('<<<') < argv0.index('>>>') + cv = '' + p0 = argv0.index('<<<') || -1 # Server response + p1 = argv0.index('>>>') || -1 # Sent command + if p0 < p1 # An SMTP server response starting with '<<<' is the first esmtp << table.call cursession = esmtp[-1] cursession['command'] = 'CONN' - argv0.sub!(/\A.+?<< -1 + else + # An SMTP command starting with '>>>' is the first + argv0 = argv0[p1, argv0.size] if p1 > -1 end # 3. Remove unused lines, concatenate folded lines - argv0.sub!(/\n\n.+\z/m, '') # Remove strings from the first blank line to the tail - argv0.gsub!(/\n[ ]+/m, ' ') # Concatenate folded lines to each previous line + argv0 = argv0[0, argv0.index("\n\n") - 1] # Remove strings from the first blank line to the tail + argv0.gsub!(/\n[ ]+/m, ' ') # Concatenate folded lines to each previous line argv0.split("\n").each do |e| # 4. Read each SMTP command and server response @@ -87,7 +96,9 @@ def rise(argv0 = '', argv1 = '>>>', argv2 = '<<<') end else # SMTP server sent a response "<<< response text" - next unless e.start_with?('<<< ') + p = e.index('<<< '); next unless p == 0 + + e = e[4, e.size] e.sub!(/\A<<<[ ]/, '') if cv = e.match(/\A([2-5]\d\d)[ ]/) then cursession['response']['reply'] = cv[1] end From 61c9fb247e5ead610f8cb59b24a72f878779b6d4 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:39:21 +0900 Subject: [PATCH 24/37] Update test results for Sisimai::Lhost::* --- test/private/lhost-activehunter.rb | 4 +-- test/private/lhost-amazonses.rb | 4 +-- test/private/lhost-amazonworkmail.rb | 2 +- test/private/lhost-exim.rb | 2 +- test/private/lhost-mailmarshalsmtp.rb | 4 +-- test/private/lhost-messagelabs.rb | 2 +- test/private/lhost-messagingserver.rb | 6 ++--- test/private/lhost-postfix.rb | 6 ++--- test/private/lhost-qmail.rb | 2 +- test/private/lhost-sendmail.rb | 36 +++++++++++++-------------- test/private/lhost-v5sendmail.rb | 30 +++++++++++----------- test/public/lhost-amazonses.rb | 2 +- test/public/lhost-amazonworkmail.rb | 4 +-- test/public/lhost-exim.rb | 2 +- test/public/lhost-messagelabs.rb | 2 +- test/public/lhost-messagingserver.rb | 4 +-- test/public/lhost-postfix.rb | 4 +-- test/public/lhost-sendmail.rb | 10 ++++---- test/public/lhost-v5sendmail.rb | 2 +- 19 files changed, 64 insertions(+), 64 deletions(-) diff --git a/test/private/lhost-activehunter.rb b/test/private/lhost-activehunter.rb index b6cb3e4f..35b1de5c 100644 --- a/test/private/lhost-activehunter.rb +++ b/test/private/lhost-activehunter.rb @@ -4,14 +4,14 @@ module Activehunter # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] '01001' => [['5.0.910', '550', 'filtered', false]], '01002' => [['5.1.1', '550', 'userunknown', true]], - '01003' => [['5.0.910', '553', 'filtered', false]], + '01003' => [['5.3.0', '553', 'filtered', false]], '01004' => [['5.7.17', '550', 'filtered', false]], '01005' => [['5.1.1', '550', 'userunknown', true]], '01006' => [['5.1.1', '550', 'userunknown', true]], '01007' => [['5.0.910', '550', 'filtered', false]], '01008' => [['5.0.910', '550', 'filtered', false]], '01009' => [['5.1.1', '550', 'userunknown', true]], - '01010' => [['5.0.910', '553', 'filtered', false]], + '01010' => [['5.3.0', '553', 'filtered', false]], '01011' => [['5.7.17', '550', 'filtered', false]], '01012' => [['5.1.1', '550', 'userunknown', true]], } diff --git a/test/private/lhost-amazonses.rb b/test/private/lhost-amazonses.rb index e7e0d069..0d90701a 100644 --- a/test/private/lhost-amazonses.rb +++ b/test/private/lhost-amazonses.rb @@ -21,7 +21,7 @@ module AmazonSES '01017' => [['2.6.0', '250', 'delivered', false]], '01018' => [['2.6.0', '250', 'delivered', false]], '01019' => [['5.7.1', '554', 'blocked', false]], - '01020' => [['4.4.7', '', 'expired', false]], + '01020' => [['4.4.2', '421', 'expired', false]], '01021' => [['5.4.4', '550', 'hostunknown', true]], '01022' => [['5.5.1', '550', 'blocked', false]], '01023' => [['5.7.1', '550', 'suspend', false]], @@ -30,7 +30,7 @@ module AmazonSES '01026' => [['5.7.1', '554', 'norelaying', false]], '01027' => [['5.2.2', '552', 'mailboxfull', false]], '01028' => [['5.4.7', '', 'expired', false]], - '01029' => [['5.3.0', '550', 'filtered', false]], + '01029' => [['5.1.0', '550', 'userunknown', true]], '01030' => [['2.6.0', '250', 'delivered', false]], '01031' => [['2.6.0', '250', 'delivered', false]], } diff --git a/test/private/lhost-amazonworkmail.rb b/test/private/lhost-amazonworkmail.rb index ebbed8be..1f8aaeef 100644 --- a/test/private/lhost-amazonworkmail.rb +++ b/test/private/lhost-amazonworkmail.rb @@ -6,7 +6,7 @@ module AmazonWorkMail '01002' => [['5.2.1', '550', 'filtered', false]], '01003' => [['5.3.5', '550', 'systemerror', false]], '01004' => [['5.2.2', '550', 'mailboxfull', false]], - '01005' => [['4.4.7', '421', 'expired', false]], + '01005' => [['4.4.2', '421', 'expired', false]], '01006' => [['5.2.2', '550', 'mailboxfull', false]], } end diff --git a/test/private/lhost-exim.rb b/test/private/lhost-exim.rb index ee40cfb3..4cc86529 100644 --- a/test/private/lhost-exim.rb +++ b/test/private/lhost-exim.rb @@ -111,7 +111,7 @@ module Exim '01109' => [['5.7.1', '554', 'userunknown', true]], '01110' => [['5.0.912', '', 'hostunknown', true], ['5.0.912', '', 'hostunknown', true]], - '01111' => [['5.0.971', '571', 'blocked', false]], + '01111' => [['5.0.971', '', 'blocked', false]], '01112' => [['5.0.971', '554', 'blocked', false]], '01113' => [['5.7.1', '554', 'blocked', false]], '01114' => [['5.0.971', '550', 'blocked', false]], diff --git a/test/private/lhost-mailmarshalsmtp.rb b/test/private/lhost-mailmarshalsmtp.rb index e2d841d8..c868709f 100644 --- a/test/private/lhost-mailmarshalsmtp.rb +++ b/test/private/lhost-mailmarshalsmtp.rb @@ -2,8 +2,8 @@ module LhostEngineTest::Private module MailMarshalSMTP IsExpected = { # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] - '01001' => [['5.0.910', '553', 'filtered', false], - ['5.0.910', '553', 'filtered', false]], + '01001' => [['5.3.0', '553', 'filtered', false], + ['5.3.0', '553', 'filtered', false]], '01002' => [['5.1.1', '550', 'userunknown', true]], } end diff --git a/test/private/lhost-messagelabs.rb b/test/private/lhost-messagelabs.rb index b75ae482..4b7f9ae5 100644 --- a/test/private/lhost-messagelabs.rb +++ b/test/private/lhost-messagelabs.rb @@ -4,7 +4,7 @@ module MessageLabs # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] '01001' => [['5.1.1', '550', 'userunknown', true]], '01002' => [['5.0.0', '550', 'securityerror', false]], - '01003' => [['5.0.0', '542', 'userunknown', true]], + '01003' => [['5.0.0', '', 'userunknown', true]], '01004' => [['5.0.0', '550', 'userunknown', true]], } end diff --git a/test/private/lhost-messagingserver.rb b/test/private/lhost-messagingserver.rb index 743351e2..24194869 100644 --- a/test/private/lhost-messagingserver.rb +++ b/test/private/lhost-messagingserver.rb @@ -9,17 +9,17 @@ module MessagingServer '01004' => [['5.2.2', '550', 'mailboxfull', false]], '01005' => [['5.4.4', '', 'hostunknown', true]], '01006' => [['5.7.1', '550', 'filtered', false]], - '01007' => [['5.2.0', '522', 'mailboxfull', false]], + '01007' => [['5.2.0', '', 'mailboxfull', false]], '01008' => [['5.2.1', '550', 'filtered', false]], '01009' => [['5.0.0', '', 'mailboxfull', false]], - '01010' => [['5.2.0', '522', 'mailboxfull', false]], + '01010' => [['5.2.0', '', 'mailboxfull', false]], '01011' => [['4.4.7', '', 'expired', false]], '01012' => [['5.0.0', '550', 'filtered', false]], '01013' => [['4.2.2', '', 'mailboxfull', false]], '01014' => [['4.2.2', '', 'mailboxfull', false]], '01015' => [['5.0.0', '550', 'filtered', false]], '01016' => [['5.0.0', '550', 'userunknown', true]], - '01017' => [['5.0.932', '', 'notaccept', true]], + '01017' => [['5.1.10', '', 'notaccept', true]], '01018' => [['5.1.8', '501', 'rejected', false]], '01019' => [['4.2.2', '', 'mailboxfull', false]], } diff --git a/test/private/lhost-postfix.rb b/test/private/lhost-postfix.rb index 948bb365..39859ca2 100644 --- a/test/private/lhost-postfix.rb +++ b/test/private/lhost-postfix.rb @@ -62,7 +62,7 @@ module Postfix '01054' => [['5.1.1', '', 'userunknown', true]], '01055' => [['5.2.1', '550', 'filtered', false]], '01056' => [['5.1.1', '', 'mailererror', false]], - '01057' => [['5.1.1', '550', 'userunknown', true], + '01057' => [['5.2.1', '550', 'userunknown', true], ['5.1.1', '550', 'userunknown', true]], '01058' => [['5.0.0', '550', 'filtered', false]], '01059' => [['5.1.1', '550', 'userunknown', true]], @@ -174,7 +174,7 @@ module Postfix '01165' => [['5.5.0', '550', 'userunknown', true]], '01166' => [['5.0.0', '550', 'userunknown', true]], '01167' => [['4.0.0', '', 'blocked', false]], - '01168' => [['5.0.0', '571', 'rejected', false]], + '01168' => [['5.0.0', '', 'rejected', false]], '01169' => [['5.0.0', '550', 'userunknown', true]], '01170' => [['5.0.0', '550', 'blocked', false]], '01171' => [['5.2.0', '', 'mailboxfull', false]], @@ -237,7 +237,7 @@ module Postfix '01228' => [['5.7.1', '554', 'authfailure', false]], '01229' => [['5.7.1', '550', 'authfailure', false]], '01230' => [['5.7.1', '550', 'authfailure', false]], - '01231' => [['5.7.1', '550', 'authfailure', false], + '01231' => [['5.7.9', '550', 'policyviolation', false], ['5.7.1', '550', 'authfailure', false], ['5.7.1', '550', 'authfailure', false]], '01232' => [['4.7.0', '421', 'blocked', false]], diff --git a/test/private/lhost-qmail.rb b/test/private/lhost-qmail.rb index 3b7b21cf..6b21a749 100644 --- a/test/private/lhost-qmail.rb +++ b/test/private/lhost-qmail.rb @@ -19,7 +19,7 @@ module Qmail '01015' => [['5.7.1', '550', 'rejected', false]], '01016' => [['5.1.2', '', 'hostunknown', true]], '01017' => [['5.1.1', '550', 'userunknown', true]], - '01018' => [['5.1.1', '511', 'userunknown', true]], + '01018' => [['5.1.1', '', 'userunknown', true]], '01019' => [['5.0.922', '', 'mailboxfull', false]], '01020' => [['5.0.910', '554', 'filtered', false]], '01021' => [['5.1.1', '', 'userunknown', true]], diff --git a/test/private/lhost-sendmail.rb b/test/private/lhost-sendmail.rb index 092ad905..753f9fbe 100644 --- a/test/private/lhost-sendmail.rb +++ b/test/private/lhost-sendmail.rb @@ -29,20 +29,20 @@ module Sendmail '01023' => [['4.4.7', '', 'expired', false]], '01024' => [['5.0.0', '554', 'filtered', false]], '01025' => [['5.0.0', '534', 'mesgtoobig', false]], - '01026' => [['5.0.0', '517', 'blocked', false]], - '01027' => [['5.0.0', '517', 'rejected', false]], + '01026' => [['5.0.0', '', 'blocked', false]], + '01027' => [['5.0.0', '', 'rejected', false]], '01028' => [['5.7.1', '554', 'norelaying', false]], '01029' => [['5.2.0', '550', 'spamdetected', false]], - '01030' => [['5.0.0', '', 'suspend', false]], - '01031' => [['5.0.0', '', 'suspend', false]], - '01032' => [['5.0.0', '554', 'mailererror', false]], - '01033' => [['5.0.0', '554', 'mailererror', false]], - '01034' => [['5.0.0', '554', 'mailererror', false]], - '01035' => [['5.1.1', '511', 'userunknown', true]], + '01030' => [['5.0.0', '554', 'suspend', false]], + '01031' => [['5.0.0', '554', 'suspend', false]], + '01032' => [['5.3.0', '554', 'mailererror', false]], + '01033' => [['5.3.0', '554', 'mailererror', false]], + '01034' => [['5.3.0', '554', 'mailererror', false]], + '01035' => [['5.1.1', '503', 'userunknown', true]], '01036' => [['5.0.0', '554', 'filtered', false]], '01037' => [['5.0.0', '554', 'filtered', false]], '01038' => [['5.1.1', '550', 'userunknown', true]], - '01039' => [['5.1.1', '550', 'userunknown', true], + '01039' => [['5.2.1', '550', 'filtered', false], ['5.1.1', '550', 'userunknown', true]], '01040' => [['5.1.1', '550', 'userunknown', true]], '01041' => [['5.7.1', '550', 'filtered', false]], @@ -130,7 +130,7 @@ module Sendmail '01124' => [['4.4.1', '', 'expired', false]], '01125' => [['5.3.4', '552', 'mesgtoobig', false]], '01127' => [['5.1.1', '550', 'userunknown', true]], - '01129' => [['5.1.6', '', 'hasmoved', true]], + '01129' => [['5.1.6', '551', 'hasmoved', true]], '01130' => [['5.1.1', '550', 'userunknown', true]], '01131' => [['5.2.0', '550', 'filtered', false], ['5.2.0', '550', 'filtered', false], @@ -140,7 +140,7 @@ module Sendmail '01134' => [['5.3.4', '552', 'mesgtoobig', false]], '01135' => [['5.1.1', '550', 'userunknown', true]], '01136' => [['5.1.2', '550', 'hostunknown', true]], - '01137' => [['5.2.2', '550', 'mailboxfull', false], + '01137' => [['5.1.1', '550', 'userunknown', true], ['5.2.2', '550', 'mailboxfull', false]], '01138' => [['5.2.0', '550', 'filtered', false]], '01139' => [['5.2.0', '550', 'filtered', false]], @@ -157,7 +157,7 @@ module Sendmail '01150' => [['5.1.1', '550', 'userunknown', true]], '01151' => [['5.2.2', '552', 'mailboxfull', false]], '01152' => [['5.3.0', '550', 'systemerror', false]], - '01153' => [['5.0.0', '554', 'mailererror', false]], + '01153' => [['5.3.0', '554', 'mailererror', false]], '01154' => [['5.1.1', '550', 'userunknown', true]], '01155' => [['5.3.4', '552', 'mesgtoobig', false]], '01156' => [['5.1.1', '550', 'userunknown', true]], @@ -165,7 +165,7 @@ module Sendmail '01159' => [['4.2.2', '452', 'mailboxfull', false]], '01160' => [['5.3.0', '553', 'filtered', false]], '01161' => [['5.1.1', '550', 'userunknown', true]], - '01162' => [['5.2.1', '550', 'filtered', false], + '01162' => [['5.1.1', '550', 'userunknown', true], ['5.2.1', '550', 'filtered', false]], '01163' => [['5.1.1', '550', 'userunknown', true]], '01164' => [['5.1.8', '553', 'rejected', false]], @@ -180,8 +180,8 @@ module Sendmail '01173' => [['5.1.1', '550', 'userunknown', true]], '01174' => [['5.1.2', '550', 'hostunknown', true]], '01175' => [['5.5.0', '554', 'blocked', false]], - '01176' => [['5.1.6', '', 'hasmoved', true]], - '01177' => [['5.0.0', '554', 'mailererror', false]], + '01176' => [['5.1.6', '551', 'hasmoved', true]], + '01177' => [['5.3.0', '554', 'mailererror', false]], '01178' => [['5.1.2', '550', 'hostunknown', true]], '01179' => [['5.1.1', '550', 'userunknown', true]], '01181' => [['5.3.4', '552', 'mesgtoobig', false]], @@ -190,7 +190,7 @@ module Sendmail '01184' => [['5.0.0', '554', 'filtered', false]], '01185' => [['4.4.7', '', 'networkerror', false]], '01186' => [['5.7.0', '552', 'policyviolation', false]], - '01187' => [['5.7.0', '', 'blocked', false]], + '01187' => [['4.7.0', '421', 'blocked', false]], '01188' => [['5.1.1', '550', 'userunknown', true]], '01189' => [['4.4.7', '', 'expired', false]], '01190' => [['5.7.1', '550', 'spamdetected', false]], @@ -220,8 +220,8 @@ module Sendmail '01214' => [['5.1.1', '550', 'userunknown', true]], '01215' => [['5.1.1', '550', 'userunknown', true]], '01216' => [['5.1.1', '550', 'userunknown', true]], - '01217' => [['5.5.0', '554', 'blocked', false]], - '01218' => [['5.5.0', '554', 'blocked', false]], + '01217' => [['4.7.0', '421', 'blocked', false]], + '01218' => [['4.7.0', '421', 'blocked', false]], '01219' => [['5.7.27', '550', 'notaccept', true]], '01220' => [['5.7.1', '550', 'policyviolation', false]], '01221' => [['5.6.0', '552', 'contenterror', false]], diff --git a/test/private/lhost-v5sendmail.rb b/test/private/lhost-v5sendmail.rb index 3591b105..3d4b488a 100644 --- a/test/private/lhost-v5sendmail.rb +++ b/test/private/lhost-v5sendmail.rb @@ -35,7 +35,7 @@ module V5sendmail '01017' => [['5.0.911', '550', 'userunknown', true]], '01018' => [['5.0.911', '550', 'userunknown', true], ['5.0.912', '550', 'hostunknown', true]], - '01019' => [['5.0.910', '', 'filtered', false]], + '01019' => [['5.0.910', '550', 'filtered', false]], '01020' => [['5.0.911', '550', 'userunknown', true]], '01021' => [['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true], @@ -184,7 +184,7 @@ module V5sendmail '01067' => [['5.0.912', '550', 'hostunknown', true]], '01068' => [['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true]], - '01069' => [['5.0.910', '', 'filtered', false]], + '01069' => [['5.0.910', '550', 'filtered', false]], '01071' => [['5.0.912', '550', 'hostunknown', true]], '01072' => [['5.0.912', '550', 'hostunknown', true], ['5.0.911', '550', 'userunknown', true], @@ -201,7 +201,7 @@ module V5sendmail ['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true]], - '01074' => [['5.0.930', '', 'systemerror', false], + '01074' => [['4.0.930', '421', 'systemerror', false], ['5.0.911', '550', 'userunknown', true]], '01075' => [['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true]], @@ -320,39 +320,39 @@ module V5sendmail '01110' => [['5.0.912', '550', 'hostunknown', true]], '01111' => [['5.0.911', '550', 'userunknown', true]], '01112' => [['5.0.911', '550', 'userunknown', true]], - '01113' => [['5.0.971', '', 'blocked', false]], + '01113' => [['4.0.971', '421', 'blocked', false]], '01114' => [['5.0.912', '550', 'hostunknown', true]], - '01115' => [['5.0.944', '', 'networkerror', false]], + '01115' => [['4.0.944', '421', 'networkerror', false]], '01116' => [['5.0.912', '550', 'hostunknown', true]], - '01117' => [['5.0.971', '', 'blocked', false]], + '01117' => [['4.0.971', '421', 'blocked', false]], '01118' => [['5.0.911', '550', 'userunknown', true], ['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true]], - '01119' => [['5.0.947', '', 'expired', false]], + '01119' => [['4.0.947', '421', 'expired', false]], '01120' => [['5.0.912', '550', 'hostunknown', true], ['5.0.911', '550', 'userunknown', true]], '01121' => [['5.0.912', '550', 'hostunknown', true]], - '01122' => [['5.0.971', '', 'blocked', false]], + '01122' => [['4.0.971', '421', 'blocked', false]], '01123' => [['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true]], - '01124' => [['5.0.947', '', 'expired', false]], - '01125' => [['5.0.947', '', 'expired', false]], + '01124' => [['4.0.947', '421', 'expired', false]], + '01125' => [['4.0.947', '421', 'expired', false]], '01126' => [['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true], ['5.0.911', '550', 'userunknown', true]], - '01127' => [['5.0.947', '', 'expired', false]], + '01127' => [['4.0.947', '421', 'expired', false]], '01128' => [['5.0.909', '550', 'norelaying', false]], '01129' => [['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true]], - '01130' => [['5.0.947', '', 'expired', false]], + '01130' => [['4.0.947', '421', 'expired', false]], '01131' => [['5.0.912', '550', 'hostunknown', true], ['5.0.911', '550', 'userunknown', true]], - '01132' => [['5.0.910', '', 'filtered', false]], + '01132' => [['5.0.910', '550', 'filtered', false]], '01133' => [['5.0.912', '550', 'hostunknown', true]], - '01134' => [['5.0.947', '', 'expired', false]], + '01134' => [['4.0.947', '421', 'expired', false]], '01135' => [['5.0.912', '550', 'hostunknown', true]], '01136' => [['5.0.912', '550', 'hostunknown', true]], '01137' => [['5.0.912', '550', 'hostunknown', true], @@ -368,7 +368,7 @@ module V5sendmail '01141' => [['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true], ['5.0.912', '550', 'hostunknown', true]], - '01142' => [['5.0.930', '', 'systemerror', false], + '01142' => [['4.0.930', '421', 'systemerror', false], ['5.0.911', '550', 'userunknown', true]], '01143' => [['5.0.912', '550', 'hostunknown', true]], '01144' => [['5.0.911', '550', 'userunknown', true], diff --git a/test/public/lhost-amazonses.rb b/test/public/lhost-amazonses.rb index a3a17e05..67e4f5c9 100644 --- a/test/public/lhost-amazonses.rb +++ b/test/public/lhost-amazonses.rb @@ -17,7 +17,7 @@ module AmazonSES '14' => [['5.7.1', '554', 'blocked', false]], '15' => [['5.7.1', '554', 'blocked', false]], '16' => [['5.7.1', '521', 'blocked', false]], - '17' => [['4.4.7', '', 'expired', false]], + '17' => [['4.4.2', '421', 'expired', false]], '18' => [['5.4.4', '550', 'hostunknown', true]], '19' => [['5.7.1', '550', 'suspend', false]], '20' => [['5.2.1', '550', 'suspend', false]], diff --git a/test/public/lhost-amazonworkmail.rb b/test/public/lhost-amazonworkmail.rb index 04ded20e..4c31ebff 100644 --- a/test/public/lhost-amazonworkmail.rb +++ b/test/public/lhost-amazonworkmail.rb @@ -6,8 +6,8 @@ module AmazonWorkMail '02' => [['5.2.1', '550', 'filtered', false]], '03' => [['5.3.5', '550', 'systemerror', false]], '04' => [['5.2.2', '550', 'mailboxfull', false]], - '05' => [['4.4.7', '421', 'expired', false]], - '07' => [['4.4.7', '421', 'expired', false]], + '05' => [['4.4.2', '421', 'expired', false]], + '07' => [['4.4.2', '421', 'expired', false]], '08' => [['5.2.2', '550', 'mailboxfull', false]], } end diff --git a/test/public/lhost-exim.rb b/test/public/lhost-exim.rb index 08867bee..85fd1d34 100644 --- a/test/public/lhost-exim.rb +++ b/test/public/lhost-exim.rb @@ -14,7 +14,7 @@ module Exim '29' => [['5.0.0', '550', 'authfailure', false]], '30' => [['5.7.1', '554', 'userunknown', true]], '31' => [['5.0.912', '', 'hostunknown', true]], - '32' => [['5.0.971', '571', 'blocked', false]], + '32' => [['5.0.971', '', 'blocked', false]], '33' => [['5.0.971', '554', 'blocked', false]], '34' => [['5.7.1', '554', 'blocked', false]], '35' => [['5.0.971', '550', 'blocked', false]], diff --git a/test/public/lhost-messagelabs.rb b/test/public/lhost-messagelabs.rb index 8fe593d4..847291e0 100644 --- a/test/public/lhost-messagelabs.rb +++ b/test/public/lhost-messagelabs.rb @@ -4,7 +4,7 @@ module MessageLabs # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] '01' => [['5.0.0', '550', 'securityerror', false]], '02' => [['5.0.0', '550', 'userunknown', true]], - '03' => [['5.0.0', '542', 'userunknown', true]], + '03' => [['5.0.0', '', 'userunknown', true]], } end end diff --git a/test/public/lhost-messagingserver.rb b/test/public/lhost-messagingserver.rb index d5a5c8ab..52be70b4 100644 --- a/test/public/lhost-messagingserver.rb +++ b/test/public/lhost-messagingserver.rb @@ -3,7 +3,7 @@ module MessagingServer IsExpected = { # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] '01' => [['5.1.1', '550', 'userunknown', true]], - '02' => [['5.2.0', '522', 'mailboxfull', false]], + '02' => [['5.2.0', '', 'mailboxfull', false]], '03' => [['5.7.1', '550', 'filtered', false], ['5.7.1', '550', 'filtered', false]], '04' => [['5.2.2', '550', 'mailboxfull', false]], @@ -12,7 +12,7 @@ module MessagingServer '07' => [['4.4.7', '', 'expired', false]], '08' => [['5.0.0', '550', 'filtered', false]], '09' => [['5.0.0', '550', 'userunknown', true]], - '10' => [['5.0.932', '', 'notaccept', true]], + '10' => [['5.1.10', '', 'notaccept', true]], '11' => [['5.1.8', '501', 'rejected', false]], '12' => [['4.2.2', '', 'mailboxfull', false]], } diff --git a/test/public/lhost-postfix.rb b/test/public/lhost-postfix.rb index 1dd5a0fc..b832e9ed 100644 --- a/test/public/lhost-postfix.rb +++ b/test/public/lhost-postfix.rb @@ -3,7 +3,7 @@ module Postfix IsExpected = { # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] '01' => [['5.1.1', '', 'mailererror', false]], - '02' => [['5.1.1', '550', 'userunknown', true], + '02' => [['5.2.1', '550', 'userunknown', true], ['5.1.1', '550', 'userunknown', true]], '03' => [['5.0.0', '550', 'filtered', false]], '04' => [['5.1.1', '550', 'userunknown', true]], @@ -15,7 +15,7 @@ module Postfix '10' => [['5.1.8', '553', 'rejected', false]], '11' => [['5.1.8', '553', 'rejected', false], ['5.1.8', '553', 'rejected', false]], - '13' => [['5.2.2', '550', 'mailboxfull', false], + '13' => [['5.2.1', '550', 'userunknown', true], ['5.2.2', '550', 'mailboxfull', false]], '14' => [['5.1.1', '', 'userunknown', true]], '15' => [['4.4.1', '', 'expired', false]], diff --git a/test/public/lhost-sendmail.rb b/test/public/lhost-sendmail.rb index 8626925d..bacf8f5f 100644 --- a/test/public/lhost-sendmail.rb +++ b/test/public/lhost-sendmail.rb @@ -3,7 +3,7 @@ module Sendmail IsExpected = { # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] '01' => [['5.1.1', '550', 'userunknown', true]], - '02' => [['5.2.1', '550', 'filtered', false], + '02' => [['5.1.1', '550', 'userunknown', true], ['5.2.1', '550', 'filtered', false]], '03' => [['5.1.1', '550', 'userunknown', true]], '04' => [['5.1.8', '553', 'rejected', false]], @@ -19,12 +19,12 @@ module Sendmail '14' => [['5.1.1', '550', 'userunknown', true]], '15' => [['5.1.2', '550', 'hostunknown', true]], '16' => [['5.5.0', '554', 'blocked', false]], - '17' => [['5.1.6', '', 'hasmoved', true]], - '18' => [['5.0.0', '554', 'mailererror', false]], + '17' => [['5.1.6', '551', 'hasmoved', true]], + '18' => [['5.3.0', '554', 'mailererror', false]], '19' => [['5.2.0', '550', 'filtered', false]], '20' => [['5.4.6', '554', 'networkerror', false]], '21' => [['4.4.7', '', 'blocked', false]], - '22' => [['5.1.6', '', 'hasmoved', true]], + '22' => [['5.1.6', '551', 'hasmoved', true]], '24' => [['5.1.2', '550', 'hostunknown', true]], '25' => [['5.1.1', '550', 'userunknown', true]], '26' => [['5.1.1', '550', 'userunknown', true]], @@ -58,7 +58,7 @@ module Sendmail '53' => [['5.0.0', '550', 'securityerror', false]], '54' => [['4.4.7', '', 'expired', false]], '55' => [['4.5.0', '451', 'mailererror', false]], - '56' => [['5.5.0', '554', 'blocked', false]], + '56' => [['4.7.0', '421', 'blocked', false]], '57' => [['5.7.27', '550', 'notaccept', true]], '58' => [['5.7.1', '550', 'authfailure', false]], '59' => [['5.7.1', '550', 'authfailure', false]], diff --git a/test/public/lhost-v5sendmail.rb b/test/public/lhost-v5sendmail.rb index 151894f7..fc316ddc 100644 --- a/test/public/lhost-v5sendmail.rb +++ b/test/public/lhost-v5sendmail.rb @@ -2,7 +2,7 @@ module LhostEngineTest::Public module V5sendmail IsExpected = { # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] - '01' => [['5.0.947', '', 'expired', false]], + '01' => [['4.0.947', '421', 'expired', false]], '02' => [['5.0.912', '550', 'hostunknown', true]], '03' => [['5.0.911', '550', 'userunknown', true]], '04' => [['5.0.912', '550', 'hostunknown', true], From 8aa93a1b8734f1df99af94c1a66d5f869d685400 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:40:27 +0900 Subject: [PATCH 25/37] Remove a blank line --- lib/sisimai/string.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/sisimai/string.rb b/lib/sisimai/string.rb index 67bade37..2f67e715 100644 --- a/lib/sisimai/string.rb +++ b/lib/sisimai/string.rb @@ -63,7 +63,6 @@ def aligned(argv1, argv2) align = -1 right = 0 - argv2.each do |e| # Get the position of each element in the 1st argument using index() p = argv1.index(e, align + 1) From 94fa800288215239a794e88fa6c571360486330c Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:41:11 +0900 Subject: [PATCH 26/37] Some modules have been renamed, reduce regular expressions --- lib/sisimai/rhost.rb | 37 ++++++++++++++--------------------- lib/sisimai/rhost/godaddy.rb | 9 +++++---- lib/sisimai/rhost/iua.rb | 8 ++++---- lib/sisimai/rhost/kddi.rb | 2 +- lib/sisimai/rhost/mimecast.rb | 2 +- 5 files changed, 26 insertions(+), 32 deletions(-) diff --git a/lib/sisimai/rhost.rb b/lib/sisimai/rhost.rb index 696285f0..4ead3c49 100644 --- a/lib/sisimai/rhost.rb +++ b/lib/sisimai/rhost.rb @@ -5,23 +5,17 @@ module Sisimai module Rhost class << self RhostClass = { - 'cox.net' => 'Cox', - '.prod.outlook.com' => 'ExchangeOnline', - '.protection.outlook.com' => 'ExchangeOnline', - 'laposte.net' => 'FrancePTT', - 'orange.fr' => 'FrancePTT', - 'wanadoo.fr' => 'FrancePTT', - 'smtp.secureserver.net' => 'GoDaddy', - 'mailstore1.secureserver.net' => 'GoDaddy', - 'aspmx.l.google.com' => 'GoogleApps', - 'gmail-smtp-in.l.google.com' => 'GoogleApps', - '.email.ua' => 'IUA', - 'lsean.ezweb.ne.jp' => 'KDDI', - 'msmx.au.com' => 'KDDI', - '.mimecast.com' => 'Mimecast', - 'mfsmax.docomo.ne.jp' => 'NTTDOCOMO', - 'charter.net' => 'Spectrum', - '.qq.com' => 'TencentQQ', + 'Cox' => ['cox.net'], + 'FrancePTT' => ['.laposte.net', '.orange.fr', '.wanadoo.fr'], + 'GoDaddy' => ['smtp.secureserver.net', 'mailstore1.secureserver.net'], + 'Google' => ['aspmx.l.google.com', 'gmail-smtp-in.l.google.com'], + 'IUA' => ['.email.ua'], + 'KDDI' => ['.ezweb.ne.jp', 'msmx.au.com'], + 'Microsoft' => ['.prod.outlook.com', '.protection.outlook.com'], + 'Mimecast' => ['.mimecast.com'], + 'NTTDOCOMO' => ['mfsmax.docomo.ne.jp'], + 'Spectrum' => ['charter.net'], + 'Tencent' => ['.qq.com'], }.freeze # The value of "rhost" is listed in RhostClass or not @@ -33,10 +27,9 @@ def match(rhost) host0 = rhost.downcase match = false - RhostClass.each_key do |e| # Try to match with each key of RhostClass - next unless host0.end_with?(e) + next unless RhostClass[e].any? { |a| host0.end_with?(a) } match = true break end @@ -54,9 +47,9 @@ def get(argvs, proxy = nil) RhostClass.each_key do |e| # Try to match with each key of RhostClass - next unless remotehost.end_with?(e) - modulename = 'Sisimai::Rhost::' << RhostClass[e] - rhostclass = modulename.gsub('::', '/').downcase + next unless RhostClass[e].any? { |a| remotehost.end_with?(a) } + modulename = 'Sisimai::Rhost::' << e + rhostclass = 'sisimai/rhost/' << e.downcase break end return nil if rhostclass.empty? diff --git a/lib/sisimai/rhost/godaddy.rb b/lib/sisimai/rhost/godaddy.rb index 9f74b14d..e40d782a 100644 --- a/lib/sisimai/rhost/godaddy.rb +++ b/lib/sisimai/rhost/godaddy.rb @@ -45,12 +45,12 @@ def get(argvs) return argvs['reason'] unless argvs['reason'].empty? statusmesg = argvs['diagnosticcode'] + positionib = statusmesg.index(' IB') || -1 reasontext = '' - if cv = statusmesg.match(/\s(IB\d{3})\b/) - # 192.0.2.22 has sent to too many recipients this hour. IB607 ... - reasontext = ErrorCodes[cv[1]] - else + # 192.0.2.22 has sent to too many recipients this hour. IB607 ... + reasontext = ErrorCodes[statusmesg[positionib + 1, 5]] || '' if positionib > 1 + if reasontext.empty? # 553 http://www.spamhaus.org/query/bl?ip=192.0.0.222 MessagesOf.each_key do |e| MessagesOf[e].each do |f| @@ -61,6 +61,7 @@ def get(argvs) break unless reasontext.empty? end end + return reasontext end diff --git a/lib/sisimai/rhost/iua.rb b/lib/sisimai/rhost/iua.rb index d771d2c9..373d26dd 100644 --- a/lib/sisimai/rhost/iua.rb +++ b/lib/sisimai/rhost/iua.rb @@ -24,11 +24,11 @@ class << self # @return [String] The bounce reason at https://www.i.ua/ def get(argvs) return argvs['reason'] unless argvs['reason'].empty? + issuedcode = argvs['diagnosticcode'].downcase + codenumber = issuedcode.index('.i.ua/err/') > 0 ? issuedcode[issuedcode.index('/err/') + 5, 2] : 0 + codenumber = codenumber[0, 1] if codenumber.index('/') == 1 - if cv = argvs['diagnosticcode'].downcase.match(%r|[.]i[.]ua/err/(\d+)|) - return ErrorCodes[cv[1]] || '' - end - return '' + return ErrorCodes[codenumber] || '' end end diff --git a/lib/sisimai/rhost/kddi.rb b/lib/sisimai/rhost/kddi.rb index 735f28a3..71487e43 100644 --- a/lib/sisimai/rhost/kddi.rb +++ b/lib/sisimai/rhost/kddi.rb @@ -19,7 +19,7 @@ def get(argvs) MessagesOf.each_key do |e| # Try to match the error message with message patterns defined in $MessagesOf - next unless statusmesg.end_with?(MessagesOf[e]) + next unless statusmesg.include?(MessagesOf[e]) reasontext = e break end diff --git a/lib/sisimai/rhost/mimecast.rb b/lib/sisimai/rhost/mimecast.rb index f87a543b..b5dad2e9 100644 --- a/lib/sisimai/rhost/mimecast.rb +++ b/lib/sisimai/rhost/mimecast.rb @@ -263,7 +263,7 @@ class << self # @param [Sisimai::Fact] argvs Parsed email object # @return [String] The bounce reason for mimecast.com def get(argvs) - return '' unless argvs['replycode'] =~ /\A[245]\d\d\z/ + return '' unless Sisimai::SMTP::Reply.test(argvs['replycode']) return '' unless argvs['diagnosticcode'] esmtperror = argvs['diagnosticcode'].downcase || '' From b2a30cbb2996a2465b3780da9b939f8c1d7801f2 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:41:47 +0900 Subject: [PATCH 27/37] Reduce regular expressions --- lib/sisimai/arf.rb | 129 ++++++++++++++++++++++++--------------------- lib/sisimai/mda.rb | 67 ++++++++++------------- 2 files changed, 96 insertions(+), 100 deletions(-) diff --git a/lib/sisimai/arf.rb b/lib/sisimai/arf.rb index 6b0a6958..1c7abcd4 100644 --- a/lib/sisimai/arf.rb +++ b/lib/sisimai/arf.rb @@ -14,23 +14,18 @@ class << self # Abusix ARF uses this is an autogenerated email abuse complaint regarding your network. Indicators = Sisimai::Lhost.INDICATORS StartingOf = { - rfc822: ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'], - report: ['Content-Type: message/feedback-report'], + rfc822: ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'], + report: ['Content-Type: message/feedback-report'], + message: [ + ['this is a', 'abuse report'], + ['this is a', 'authentication', 'failure report'], + ['this is a', ' report for'], + ['this is an authentication', 'failure report'], + ['this is an autogenerated email abuse complaint'], + ['this is an email abuse report'], + ], }.freeze - MarkingsOf = { - message: %r{\A(?> - [Tt]his[ ]is[ ]a[ ][^ ]+[ ](?:email[ ])?[Aa]buse[ ][Rr]eport - |[Tt]his[ ]is[ ]an[ ]email[ ]abuse[ ]report - |[Tt]his[ ]is[ ](?: - a[ ][^ ]+[ ]authentication[ -]failure[ ]report - |an[ ]authentication[ -]failure[ ]report - |an[ ]autogenerated[ ]email[ ]abuse[ ]complaint - |an?[ ][^ ]+[ ]report[ ]for - ) - ) - }x, - }.freeze - ReportFrom = /(?:staff[@]hotmail[.]com|complaints[@]email-abuse[.]amazonses[.]com)\z/.freeze + ReportFrom = ['staff@hotmail.com', 'complaints@email-abuse.amazonses.com']; LongFields = Sisimai::RFC5322.LONGFIELDS RFC822Head = Sisimai::RFC5322.HEADERFIELDS @@ -44,19 +39,19 @@ def is_arf(heads) return false unless heads match = false - if heads['content-type'] =~ /report-type=["]?feedback-report["]?/ + if Sisimai::String.aligned(heads['content-type'], ['report-type=', 'feedback-report']) # Content-Type: multipart/report; report-type=feedback-report; ... match = true - elsif heads['content-type'].start_with?('multipart/mixed') + elsif heads['content-type'].include?('multipart/mixed') # Microsoft (Hotmail, MSN, Live, Outlook) uses its own report format. # Amazon SES Complaints bounces - p = Sisimai::Address.s3s4(heads['from']) - if p =~ ReportFrom && heads['subject'].include?('complaint about message from ') + cv = Sisimai::Address.s3s4(heads['from']) + if heads['subject'].include?('complaint about message from ') # From: staff@hotmail.com # From: complaints@email-abuse.amazonses.com # Subject: complaint about message from 192.0.2.1 - match = true + match = true if ReportFrom.any? { |a| cv.include?(a) } end end return match @@ -116,8 +111,9 @@ def inquire(mhead, mbody) # message-id of 0000-000000000000000000000000000000000@mx # received from IP address 192.0.2.1 on # Thu, 29 Apr 2010 00:00:00 +0900 (JST) - if e =~ MarkingsOf[:message] - commondata['diagnosis'] = e if commondata['diagnosis'].empty? + p = e.downcase + if commondata['diagnosis'].empty? + commondata['diagnosis'] = e if StartingOf[:message].any? { |a| Sisimai::String.aligned(p, a) } end if readcursor == 0 @@ -135,9 +131,9 @@ def inquire(mhead, mbody) if readcursor & Indicators[:'message-rfc822'] > 0 # message/rfc822 OR text/rfc822-headers part - if cv = e.match(/X-HmXmrOriginalRecipient:[ ]*(.+)\z/) + if e.start_with?('X-HmXmrOriginalRecipient:') # Microsoft ARF: original recipient. - dscontents[-1]['recipient'] = Sisimai::Address.s3s4(cv[1]) + dscontents[-1]['recipient'] = Sisimai::Address.s3s4(e[e.index(':') + 1, e.size]) recipients += 1 # The "X-HmXmrOriginalRecipient" header appears only once so we take this opportunity @@ -145,9 +141,9 @@ def inquire(mhead, mbody) arfheaders['feedbacktype'] = 'abuse' arfheaders['agent'] = 'Microsoft Junk Mail Reporting Program' - elsif cv = e.match(/\AFrom:[ ](.+)\z/) + elsif e.start_with?('From: ') # Microsoft ARF: original sender. - commondata['from'] = Sisimai::Address.s3s4(cv[1]) if commondata['from'].empty? + commondata['from'] = Sisimai::Address.s3s4(e[6, e.size]) if commondata['from'].empty? previousfn = 'from' elsif e.start_with?(' ') @@ -189,8 +185,7 @@ def inquire(mhead, mbody) # Source-IP: 192.0.2.1 v = dscontents[-1] - if cv = e.match(/\AOriginal-Rcpt-To:[ ][<]?(.+)[>]?\z/) || - e.match(/\ARedacted-Address:[ ]([^ ].+[@])\z/) + if e.start_with?('Original-Rcpt-To: ', 'Redacted-Address: ') # Original-Rcpt-To header field is optional and may appear any number of times as appropriate: # Original-Rcpt-To: # Redacted-Address: localpart@ @@ -199,45 +194,45 @@ def inquire(mhead, mbody) dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end - v['recipient'] = Sisimai::Address.s3s4(cv[1]) + v['recipient'] = Sisimai::Address.s3s4(e[e.index(' ') + 1, e.size]) recipients += 1 - elsif cv = e.match(/\AFeedback-Type:[ ]([^ ]+)\z/) + elsif e.start_with?('Feedback-Type: ') # The header field MUST appear exactly once. # Feedback-Type: abuse - arfheaders['feedbacktype'] = cv[1] + arfheaders['feedbacktype'] = e[e.index(' ') + 1, e.size] - elsif cv = e.match(/\AAuthentication-Results:[ ](.+)\z/) + elsif e.start_with?('Authentication-Results: ') # "Authentication-Results" indicates the result of one or more authentication checks # run by the report generator. # Authentication-Results: mail.example.jp; spf=fail smtp.mail=spammer@example.com - arfheaders['authres'] = cv[1] + arfheaders['authres'] = e[e.index(' ') + 1, e.size] - elsif cv = e.match(/\AUser-Agent:[ ](.+)\z/) + elsif e.start_with?('User-Agent: ') # The header field MUST appear exactly once. # User-Agent: SomeGenerator/1.0 - arfheaders['agent'] = cv[1] + arfheaders['agent'] = e[e.index(' ') + 1, e.size] - elsif cv = e.match(/\A(?:Received|Arrival)-Date:[ ](.+)\z/) + elsif e.start_with?('Received-Date: ', 'Arrival-Date: ') # Arrival-Date header is optional and MUST NOT appear more than once. # Received-Date: Thu, 29 Apr 2010 00:00:00 JST # Arrival-Date: Thu, 29 Apr 2010 00:00:00 +0000 - arfheaders['date'] = cv[1] + arfheaders['date'] = e[e.index(' ') + 1, e.size] - elsif cv = e.match(/\AReporting-MTA:[ ]dns;[ ](.+)\z/) + elsif e.start_with?('Reporting-MTA: dns; ') # The header is optional and MUST NOT appear more than once. # Reporting-MTA: dns; mx.example.jp - commondata['rhost'] = cv[1] + commondata['rhost'] = e[e.index(';') + 2, e.size] - elsif cv = e.match(/\ASource-IP:[ ](.+)\z/) + elsif e.start_with?('Source-IP: ') # The header is optional and MUST NOT appear more than once. # Source-IP: 192.0.2.45 - arfheaders['rhost'] = cv[1] + arfheaders['rhost'] = e[e.index(' ') + 1, e.size] - elsif cv = e.match(/\AOriginal-Mail-From:[ ](.+)\z/) + elsif e.start_with?('Original-Mail-From: ') # the header is optional and MUST NOT appear more than once. # Original-Mail-From: - commondata['from'] = Sisimai::Address.s3s4(cv[1]) if commondata['from'].empty? + commondata['from'] = Sisimai::Address.s3s4(e[e.index(' ') + 1, e.size]) if commondata['from'].empty? end end @@ -248,36 +243,48 @@ def inquire(mhead, mbody) commondata['diagnosis'] << ' ' << arfheaders['authres'] end - unless recipients > 0 + if recipients == 0 # The original recipient address was not found - if cv = rfc822part.match(/^To: (.+[@].+)$/) + if Sisimai::String.aligned(rfc822part, ["\nTo: ", '@']) # pick the address from To: header in message/rfc822 part. - dscontents[-1]['recipient'] = Sisimai::Address.s3s4(cv[1]) - else - # Insert a pseudo recipient address when there is no valid recipient address in the message. - dscontents[-1]['recipient'] = Sisimai::Address.undisclosed('r') + p1 = rfc822part.index("\nTo: ") + 5 + p2 = rfc822part.index("\n", p1 + 1) + cm = p2 > 0 ? p2 - p1 : 255 + dscontents[-1]['recipient'] = Sisimai::Address.s3s4(rfc822part[p1, cm]) + recipients = 1 + end + + while true + # Insert pseudo recipient address when there is no valid recipient address in the message + # for example, + # Date: Thu, 29 Apr 2015 23:34:45 +0000 + # To: "undisclosed" + # Subject: Nyaan + # Message-ID: + dscontents[-1]['recipient'] ||= '' + break if dscontents[-1]['recipient'].include?('@') + dscontents[-1]['recipient'] = Sisimai::Address.undisclosed(true) + recipients = 1 + break end - recipients = 1 end - unless rfc822part =~ /\bFrom: [^ ]+[@][^ ]+\b/ + unless Sisimai::String.aligned(rfc822part, ['From: ', '@']) # There is no "From:" header in the original message. Append the value of "Original-Mail-From" # value as a sender address. rfc822part << 'From: ' << commondata['from'] + "\n" unless commondata['from'].empty? end - if cv = mhead['subject'].match(/complaint about message from (\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3})/) + if mhead['subject'].include?('complaint about message from ') # Microsoft ARF: remote host address. - arfheaders['rhost'] = cv[1] + arfheaders['rhost'] = mhead['subject'][mhead['subject'].rindex(' ') + 1, mhead['subject'].size] commondata['diagnosis'] = 'This is a Microsoft email abuse report for an email message received from IP' << arfheaders['rhost'] + ' on ' << mhead['date'] end dscontents.each do |e| - if e['recipient'] =~ /\A[^ ]+[@]\z/ - # AOL = http://forums.cpanel.net/f43/aol-brutal-work-71473.html - e['recipient'] = Sisimai::Address.s3s4(rcptintext) - end + # AOL = http://forums.cpanel.net/f43/aol-brutal-work-71473.html + e['recipient'] = Sisimai::Address.s3s4(rcptintext) if e['recipient'][-1, 1] == '@' arfheaders.each_key { |a| e[a] ||= arfheaders[a] || '' } e.delete('authres') @@ -294,10 +301,12 @@ def inquire(mhead, mbody) # The value of "Reporting-MTA" header e['rhost'] = commondata['rhost'] - elsif cv = e['diagnosis'].match(/\breceived from IP address ([^ ]+)/) + else + # Try to get an IP address from the error message # This is an email abuse report for an email message received from IP address 24.64.1.1 # on Thu, 29 Apr 2010 00:00:00 +0000 - e['rhost'] = cv[1] + ip = Sisimai::String.ipv4(e['diagnosis']) || [] + e['rhost'] = ip[0] if ip.size > 0 end end return { 'ds' => dscontents, 'rfc822' => rfc822part } diff --git a/lib/sisimai/mda.rb b/lib/sisimai/mda.rb index ecda9507..214dcc36 100644 --- a/lib/sisimai/mda.rb +++ b/lib/sisimai/mda.rb @@ -6,19 +6,12 @@ class << self # dovecot/src/deliver/deliver.c # 11: #define DEFAULT_MAIL_REJECTION_HUMAN_REASON \ # 12: "Your message to <%t> was automatically rejected:%n%r" - 'dovecot' => %r/\AYour message to [^ ]+ was automatically rejected:\z/, - 'mail.local' => %r/\Amail[.]local: /, - 'procmail' => %r/\Aprocmail: /, - 'maildrop' => %r/\Amaildrop: /, - 'vpopmail' => %r/\Avdelivermail: /, - 'vmailmgr' => %r/\Avdeliver: /, - }.freeze - MarkingsOf = { - message: %r{\A(?> - Your[ ]message[ ]to[ ][^ ]+[ ]was[ ]automatically[ ]rejected:\z - |(?:mail[.]local|procmail|maildrop|vdelivermail|vdeliver):[ ] - ) - }x + 'dovecot' => ['Your message to ', ' was automatically rejected:'], + 'mail.local' => ['mail.local: '], + 'procmail' => ['procmail: '], + 'maildrop' => ['maildrop: '], + 'vpopmail' => ['vdelivermail: '], + 'vmailmgr' => ['vdeliver: '], }.freeze # dovecot/src/deliver/mail-send.c:94 @@ -79,42 +72,36 @@ class << self # @return [Hash] Bounce data list and message/rfc822 part # @return [Nil] it failed to parse or the arguments are missing def inquire(mhead, mbody) + return nil unless mhead + return nil unless mbody.size > 0 return nil unless mhead['from'].downcase.start_with?('mail delivery subsystem','mailer-daemon', 'postmaster') - agentname0 = '' # [String] MDA name + deliversby = '' # [String] Mail Delivery Agent name reasonname = '' # [String] Error reason bouncemesg = '' # [String] Error message - bodyslices = mbody.split("\n") - linebuffer = [] + linebuffer = mbody.split("\n") - while e = bodyslices.shift do - # Check each line with each MDA's symbol regular expression. - if agentname0 == '' - # Try to match with each regular expression - next if e.empty? - next unless e =~ MarkingsOf[:message] + AgentNames.each_key do |e| + # Find a mail delivery agent name from the entire message body + p = mbody.index(AgentNames[e][0]) + next unless p - AgentNames.each_key do |f| - # Detect the agent name from the line - next unless e =~ AgentNames[f] - agentname0 = f - break - end + if AgentNames[e].size > 1 + # Try to find the 2nd argument + q = mbody.index(AgentNames[e][1]) + next unless q + next if p > q end - - # Append error message lines to @linebuffer - linebuffer << e - break if e.empty? + deliversby = e + break end - return nil if agentname0.empty? - return nil if linebuffer.empty? + return nil if deliversby.empty? - MessagesOf[agentname0].each_key do |e| + MessagesOf[deliversby].each_key do |e| # Detect an error reason from message patterns of the MDA. - duplicated = linebuffer.dup - while f = duplicated.shift do - # Whether the error message include each message defined in $MessagesOf - next unless MessagesOf[agentname0][e].any? { |a| f.downcase.include?(a) } + linebuffer.each do |f| + # Whether the error message include each message defined in MessagesOf + next unless MessagesOf[deliversby][e].any? { |a| f.downcase.include?(a) } reasonname = e bouncemesg = f break @@ -123,7 +110,7 @@ def inquire(mhead, mbody) end return { - 'mda' => agentname0, + 'mda' => deliversby, 'reason' => reasonname || '', 'message' => bouncemesg || '', } From 68825ce5c017bcbb5cf4f9e0aee07bc4bc839730 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:42:16 +0900 Subject: [PATCH 28/37] Reduce regular expressions using Sisimai::String.alined() --- lib/sisimai/reason/authfailure.rb | 8 +++----- lib/sisimai/reason/blocked.rb | 8 +++----- lib/sisimai/reason/filtered.rb | 13 +++++++------ lib/sisimai/reason/hostunknown.rb | 14 ++++++-------- lib/sisimai/reason/policyviolation.rb | 8 ++++++-- lib/sisimai/reason/rejected.rb | 10 +++++----- lib/sisimai/reason/securityerror.rb | 11 ++++------- lib/sisimai/reason/spamdetected.rb | 20 ++++++++------------ lib/sisimai/reason/speeding.rb | 1 + lib/sisimai/reason/suspend.rb | 1 + lib/sisimai/reason/syntaxerror.rb | 5 ++++- lib/sisimai/reason/userunknown.rb | 19 +++++++++---------- 12 files changed, 57 insertions(+), 61 deletions(-) diff --git a/lib/sisimai/reason/authfailure.rb b/lib/sisimai/reason/authfailure.rb index 65317928..3555d6ba 100644 --- a/lib/sisimai/reason/authfailure.rb +++ b/lib/sisimai/reason/authfailure.rb @@ -12,6 +12,8 @@ module Reason # Diagnostic-Code: smtp; 550 5.7.1 Email rejected per DMARC policy for example.org module AuthFailure class << self + require 'sisimai/string' + Index = [ '//spf.pobox.com', 'bad spf records for', @@ -36,11 +38,7 @@ def description; return 'Email rejected due to SPF, DKIM, DMARC failure'; end def match(argv1) return nil unless argv1 return true if Index.any? { |a| argv1.include?(a) } - return true if Pairs.any? { |a| - p = (argv1.index(a[0], 0) || -1) + 1 - q = (argv1.index(a[1], p) || -1) + 1 - p * q > 0 - } + return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) } return false end diff --git a/lib/sisimai/reason/blocked.rb b/lib/sisimai/reason/blocked.rb index 89610261..c57daa1c 100644 --- a/lib/sisimai/reason/blocked.rb +++ b/lib/sisimai/reason/blocked.rb @@ -7,6 +7,8 @@ module Reason # or the parameter of "HELO/EHLO" command. This reason has added in Sisimai 4.0.0. module Blocked class << self + require 'sisimai/string' + Index = [ ' said: 550 blocked', '//www.spamcop.net/bl.', @@ -124,11 +126,7 @@ def description; return 'Email rejected due to client IP address or a hostname'; def match(argv1) return nil unless argv1 return true if Index.any? { |a| argv1.include?(a) } - return true if Pairs.any? { |a| - p = (argv1.index(a[0], 0) || -1) + 1 - q = (argv1.index(a[1], p) || -1) + 1 - p * q > 0 - } + return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) } return true if argv1 =~ Regex return false end diff --git a/lib/sisimai/reason/filtered.rb b/lib/sisimai/reason/filtered.rb index bdabd13b..5e95dd92 100644 --- a/lib/sisimai/reason/filtered.rb +++ b/lib/sisimai/reason/filtered.rb @@ -51,18 +51,19 @@ def true(argvs) tempreason = Sisimai::SMTP::Status.name(argvs['deliverystatus']) || '' return false if tempreason == 'suspend' - diagnostic = argvs['diagnosticcode'].downcase || '' + issuedcode = argvs['diagnosticcode'].downcase || '' + thecommand = argvs['smtpcommand'] || '' if tempreason == 'filtered' # Delivery status code points "filtered". - return true if Sisimai::Reason::UserUnknown.match(diagnostic) - return true if match(diagnostic) + return true if Sisimai::Reason::UserUnknown.match(issuedcode) + return true if match(issuedcode) else # The value of "reason" isn't "filtered" when the value of "smtpcommand" is an SMTP # command to be sent before the SMTP DATA command because all the MTAs read the headers # and the entire message body after the DATA command. - return false if %w[CONN EHLO HELO MAIL RCPT].include?(argvs['smtpcommand']) - return true if match(diagnostic) - return true if Sisimai::Reason::UserUnknown.match(diagnostic) + return false if %w[CONN EHLO HELO MAIL RCPT].include?(thecommand) + return true if match(issuedcode) + return true if Sisimai::Reason::UserUnknown.match(issuedcode) end return false end diff --git a/lib/sisimai/reason/hostunknown.rb b/lib/sisimai/reason/hostunknown.rb index eecbb2dd..9b62ada3 100644 --- a/lib/sisimai/reason/hostunknown.rb +++ b/lib/sisimai/reason/hostunknown.rb @@ -6,6 +6,8 @@ module Reason # Status: field in a bounce mail is "5.1.2". module HostUnknown class << self + require 'sisimai/string' + Index = [ 'domain does not exist', 'domain is not reachable', @@ -36,11 +38,7 @@ def description; return "Delivery failed due to a domain part of a recipient's e def match(argv1) return nil unless argv1 return true if Index.any? { |a| argv1.include?(a) } - return true if Pairs.any? { |a| - p = (argv1.index(a[0], 0) || -1) + 1 - q = (argv1.index(a[1], p) || -1) + 1 - p * q > 0 - } + return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) } return false end @@ -52,17 +50,17 @@ def match(argv1) def true(argvs) return true if argvs['reason'] == 'hostunknown' - diagnostic = argvs['diagnosticcode'].downcase || '' + issuedcode = argvs['diagnosticcode'].downcase || '' statuscode = argvs['deliverystatus'] || '' if Sisimai::SMTP::Status.name(statuscode).to_s == 'hostunknown' # Status: 5.1.2 # Diagnostic-Code: SMTP; 550 Host unknown require 'sisimai/reason/networkerror' - return true unless Sisimai::Reason::NetworkError.match(diagnostic) + return true unless Sisimai::Reason::NetworkError.match(issuedcode) else # Check the value of Diagnosic-Code: header with patterns - return true if match(diagnostic) + return true if match(issuedcode) end return false diff --git a/lib/sisimai/reason/policyviolation.rb b/lib/sisimai/reason/policyviolation.rb index 077eaa06..3fd0b18b 100644 --- a/lib/sisimai/reason/policyviolation.rb +++ b/lib/sisimai/reason/policyviolation.rb @@ -14,6 +14,8 @@ module Reason # module PolicyViolation class << self + require 'sisimai/string' + Index = [ 'an illegal attachment on your message', 'because the recipient is not accepting mail with ', # AOL Phoenix @@ -37,10 +39,11 @@ class << self 'the message was rejected by organization policy', 'this message was blocked because its content presents a potential', 'we do not accept messages containing images or other attachments', - 'you have exceeded the allowable number of posts without solving a captcha', - 'you have exceeded the the allowable number of posts without solving a captcha', "you're using a mass mailer", ].freeze + Pairs = [ + ['you have exceeded the', 'allowable number of posts without solving a captcha'], + ].freeze def text; return 'policyviolation'; end def description; return 'Email rejected due to policy violation on a destination host'; end @@ -53,6 +56,7 @@ def description; return 'Email rejected due to policy violation on a destination def match(argv1) return nil unless argv1 return true if Index.any? { |a| argv1.include?(a) } + return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) } return false end diff --git a/lib/sisimai/reason/rejected.rb b/lib/sisimai/reason/rejected.rb index bcf64404..c90f3cb7 100644 --- a/lib/sisimai/reason/rejected.rb +++ b/lib/sisimai/reason/rejected.rb @@ -98,22 +98,22 @@ def true(argvs) return true if tempreason == 'rejected' # Delivery status code points "rejected". # Check the value of Diagnosic-Code: header with patterns - diagnostic = argvs['diagnosticcode'].downcase - thecommand = argvs['smtpcommand'] + issuedcode = argvs['diagnosticcode'].downcase + thecommand = argvs['smtpcommand'] || '' if thecommand == 'MAIL' # The session was rejected at 'MAIL FROM' command - return true if match(diagnostic) + return true if match(issuedcode) elsif thecommand == 'DATA' # The session was rejected at 'DATA' command if tempreason != 'userunknown' # Except "userunknown" - return true if match(diagnostic) + return true if match(issuedcode) end elsif %w[onhold undefined securityerror systemerror].include?(tempreason) # Try to match with message patterns when the temporary reason is "onhold", "undefined", # "securityerror", or "systemerror" - return true if match(diagnostic) + return true if match(issuedcode) end return false end diff --git a/lib/sisimai/reason/securityerror.rb b/lib/sisimai/reason/securityerror.rb index 6318a60c..065fbbb8 100644 --- a/lib/sisimai/reason/securityerror.rb +++ b/lib/sisimai/reason/securityerror.rb @@ -17,6 +17,8 @@ module Reason # module SecurityError class << self + require 'sisimai/string' + Index = [ 'account not subscribed to ses', 'authentication credentials invalid', @@ -34,11 +36,11 @@ class << self ].freeze Pairs = [ ['authentication failed; server ', ' said: '], # Postfix + ['authentification invalide', '305'], ['authentification requise', '402'], ['domain ', ' is a dead domain'], ['user ', ' is not authorized to perform ses:sendrawemail on resource'], ].freeze - Regex = %r/codes?[ ]d'?[ ]*authentification[ ]invalide.+[0-9a-z_]+305/.freeze def text; return 'securityerror'; end def description; return 'Email rejected due to security violation was detected on a destination host'; end @@ -50,12 +52,7 @@ def description; return 'Email rejected due to security violation was detected o def match(argv1) return nil unless argv1 return true if Index.any? { |a| argv1.include?(a) } - return true if Pairs.any? { |a| - p = (argv1.index(a[0], 0) || -1) + 1 - q = (argv1.index(a[1], p) || -1) + 1 - p * q > 0 - } - return true if argv1 =~ Regex + return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) } return false end diff --git a/lib/sisimai/reason/spamdetected.rb b/lib/sisimai/reason/spamdetected.rb index f4b68472..a3d24d04 100644 --- a/lib/sisimai/reason/spamdetected.rb +++ b/lib/sisimai/reason/spamdetected.rb @@ -10,6 +10,8 @@ module Reason # Last-Attempt-Date: Thu, 9 Apr 2008 23:34:45 +0900 (JST) module SpamDetected class << self + require 'sisimai/string' + Index = [ ' - spam', '//www.spamhaus.org/help/help_spam_16.htm', @@ -22,7 +24,6 @@ class << self 'blocked by policy: no spam please', 'blocked by spamassassin', # rejected by SpamAssassin 'blocked for abuse. see http://att.net/blocks', # AT&T - 'bulk email', 'cannot be forwarded because it was detected as spam', 'considered unsolicited bulk e-mail (spam) by our mail filters', 'content filter rejection', @@ -56,9 +57,11 @@ class << self 'probable spam', 'reject bulk.advertising', 'rejected: spamassassin score ', + 'rejected - bulk email', 'rejecting banned content', 'rejecting mail content', 'related to content with spam-like characteristics', + 'sender domain listed at ', 'sending address not accepted due to spam filter', 'spam blocked', 'spam check', @@ -103,16 +106,13 @@ class << self ['mail rejete. mail rejected. ', '506'], ['our filters rate at and above ', ' percent probability of being spam'], ['rejected by ', ' (spam)'], + ['rejected due to spam ', 'classification'], + ['rejected due to spam ', 'content'], ['rule imposed as ', ' is blacklisted on'], ['spam ', ' exceeded'], ['this message scored ', ' spam points'], ].freeze - Regex = %r{(?> - (?:\d[.]\d[.]\d|\d{3})[ ]spam\z - |rejected[ ]due[ ]to[ ]spam[ ](?:url[ ]in[ ])?(?:classification|content) - |sender[ ]domain[ ]listed[ ]at[ ][^ ]+ - ) - }x.freeze + Regex = %r/(?:\d[.]\d[.]\d|\d{3})[ ]spam\z/.freeze def text; return 'spamdetected'; end def description; return 'Email rejected by spam filter running on the remote host'; end @@ -124,11 +124,7 @@ def description; return 'Email rejected by spam filter running on the remote hos def match(argv1) return nil unless argv1 return true if Index.any? { |a| argv1.include?(a) } - return true if Pairs.any? { |a| - p = (argv1.index(a[0], 0) || -1) + 1 - q = (argv1.index(a[1], p) || -1) + 1 - p * q > 0 - } + return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) } return true if argv1 =~ Regex return false end diff --git a/lib/sisimai/reason/speeding.rb b/lib/sisimai/reason/speeding.rb index 841aa37c..a507b2c6 100644 --- a/lib/sisimai/reason/speeding.rb +++ b/lib/sisimai/reason/speeding.rb @@ -6,6 +6,7 @@ module Reason module Speeding class << self Index = [ + 'mail sent from your IP address has been temporarily rate limited', 'please try again slower', 'receiving mail at a rate that prevents additional messages from being delivered', ].freeze diff --git a/lib/sisimai/reason/suspend.rb b/lib/sisimai/reason/suspend.rb index e22c8899..2a9225b2 100644 --- a/lib/sisimai/reason/suspend.rb +++ b/lib/sisimai/reason/suspend.rb @@ -47,6 +47,7 @@ def match(argv1) def true(argvs) return nil if argvs['deliverystatus'].empty? return true if argvs['reason'] == 'suspend' + return true if argvs['replycode'].to_i == 525 return true if match(argvs['diagnosticcode'].downcase) return false end diff --git a/lib/sisimai/reason/syntaxerror.rb b/lib/sisimai/reason/syntaxerror.rb index e9581b83..6b4d2686 100644 --- a/lib/sisimai/reason/syntaxerror.rb +++ b/lib/sisimai/reason/syntaxerror.rb @@ -25,7 +25,10 @@ def match(*); return nil; end # @see http://www.ietf.org/rfc/rfc2822.txt def true(argvs) return true if argvs['reason'] == 'syntaxerror' - return true if argvs['replycode'] =~ /\A[45]0[1-7]\z/ + + reply = argvs['replycode'].to_i + return true if reply > 400 && reply < 408 + return true if reply > 500 && reply < 508 return false end diff --git a/lib/sisimai/reason/userunknown.rb b/lib/sisimai/reason/userunknown.rb index 4b8a9ea7..fdcd4a41 100644 --- a/lib/sisimai/reason/userunknown.rb +++ b/lib/sisimai/reason/userunknown.rb @@ -14,6 +14,8 @@ module Reason # RCPT TO command) module UserUnknown class << self + require 'sisimai/string' + PreMatches = %w[NoRelaying Blocked MailboxFull HasMoved Rejected NotAccept] ModulePath = { 'Sisimai::Reason::NoRelaying' => 'sisimai/reason/norelaying', @@ -61,6 +63,7 @@ class << self 'no such mailbox', 'no such person at this address', 'no such recipient', + 'no such user', 'no thank you rejected: account unavailable', 'no valid recipients, bye', @@ -117,8 +120,8 @@ class << self ['adresse d au moins un destinataire invalide. invalid recipient.', '416'], ['adresse d au moins un destinataire invalide. invalid recipient.', '418'], ['bad', 'recipient'], - ['mailbox ', ' does not exist'], - ['mailbox ', ' unavailable'], + ['mailbox ', 'does not exist'], + ['mailbox ', 'unavailable or access denied'], ['no ', ' in name directory'], ['non', 'existent user'], ['rcpt <', ' does not exist'], @@ -144,11 +147,7 @@ def description; return "Email rejected due to a local part of a recipient's ema def match(argv1) return nil unless argv1 return true if Index.any? { |a| argv1.include?(a) } - return true if Pairs.any? { |a| - p = (argv1.index(a[0], 0) || -1) + 1 - q = (argv1.index(a[1], p) || -1) + 1 - p * q > 0 - } + return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) } return false end @@ -163,7 +162,7 @@ def true(argvs) tempreason = Sisimai::SMTP::Status.name(argvs['deliverystatus']) || '' return false if tempreason == 'suspend' - diagnostic = argvs['diagnosticcode'].downcase + issuedcode = argvs['diagnosticcode'].downcase if tempreason == 'userunknown' # *.1.1 = 'Bad destination mailbox address' # Status: 5.1.1 @@ -182,7 +181,7 @@ def true(argvs) next end - next unless r.match(diagnostic) + next unless r.match(issuedcode) # Match with reason defined in Sisimai::Reason::* except UserUnknown. matchother = true break @@ -192,7 +191,7 @@ def true(argvs) elsif argvs['smtpcommand'] == 'RCPT' # When the SMTP command is not "RCPT", the session rejected by other # reason, maybe. - return true if match(diagnostic) + return true if match(issuedcode) end return false From 125a88e5a1814c6c6b5badbefa84bb69fa567c4a Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:43:24 +0900 Subject: [PATCH 29/37] Some variables have been renamed --- lib/sisimai/reason.rb | 46 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/sisimai/reason.rb b/lib/sisimai/reason.rb index 945e4998..e0e75679 100644 --- a/lib/sisimai/reason.rb +++ b/lib/sisimai/reason.rb @@ -67,7 +67,10 @@ def get(argvs) return 'delivered' if argvs['deliverystatus'].start_with?('2.') reasontext = '' - if argvs['diagnostictype'] == 'SMTP' || argvs['diagnostictype'] == '' + issuedcode = argvs['diagnosticcode'] || '' + codeformat = argvs['diagnostictype'] || '' + + if codeformat == 'SMTP' || codeformat == '' # Diagnostic-Code: SMTP; ... or empty value ClassOrder[0].each do |e| # Check the value of Diagnostic-Code: and the value of Status:, it is a deliverystats, @@ -99,8 +102,8 @@ def get(argvs) # Try to match with message patterns in Sisimai::Reason::Vacation require 'sisimai/reason/vacation' - reasontext = 'vacation' if Sisimai::Reason::Vacation.match(argvs['diagnosticcode'].downcase) - reasontext ||= 'onhold' unless argvs['diagnosticcode'].empty? + reasontext = 'vacation' if Sisimai::Reason::Vacation.match(issuedcode.downcase) + reasontext ||= 'onhold' unless issuedcode.empty? reasontext ||= 'undefined' end end @@ -115,15 +118,17 @@ def anotherone(argvs) return argvs['reason'] unless argvs['reason'].empty? require 'sisimai/smtp/status' - statuscode = argvs['deliverystatus'] || '' + issuedcode = argvs['diagnosticcode'].downcase || '' + codeformat = argvs['diagnostictype'] || '' + actiontext = argvs['action'] || '' + statuscode = argvs['deliverystatus'] || '' reasontext = Sisimai::SMTP::Status.name(statuscode) || '' catch :TRY_TO_MATCH do while true - diagnostic = argvs['diagnosticcode'].downcase || '' trytomatch = reasontext.empty? ? true : false trytomatch ||= true if GetRetried[reasontext] - trytomatch ||= true if argvs['diagnostictype'] != 'SMTP' + trytomatch ||= true if codeformat != 'SMTP' throw :TRY_TO_MATCH unless trytomatch # Could not decide the reason by the value of Status: @@ -139,25 +144,26 @@ def anotherone(argvs) next end - next unless r.match(diagnostic) + next unless r.match(issuedcode) reasontext = e.downcase break end throw :TRY_TO_MATCH unless reasontext.empty? # Check the value of Status: - v = statuscode[0, 3] - if v == '5.6' || v == '4.6' + code2digit = statuscode[0, 3] || '' + if code2digit == '5.6' || code2digit == '4.6' # X.6.0 Other or undefined media error reasontext = 'contenterror' - elsif v == '5.7' || v == '4.7' + elsif code2digit == '5.7' || code2digit == '4.7' # X.7.0 Other or undefined security status reasontext = 'securityerror' - elsif %w[X-UNIX X-POSTFIX].include?(argvs['diagnostictype']) + elsif codeformat.start_with?('X-UNIX') # Diagnostic-Code: X-UNIX; ... reasontext = 'mailererror' + else # 50X Syntax Error? require 'sisimai/reason/syntaxerror' @@ -166,7 +172,7 @@ def anotherone(argvs) throw :TRY_TO_MATCH unless reasontext.empty? # Check the value of Action: field, first - if argvs['action'].start_with?('delayed', 'expired') + if actiontext.start_with?('delayed', 'expired') # Action: delayed, expired reasontext = 'expired' else @@ -187,7 +193,7 @@ def match(argv1) return nil unless argv1 reasontext = '' - diagnostic = argv1.downcase + issuedcode = argv1.downcase # Diagnostic-Code: SMTP; ... or empty value ClassOrder[2].each do |e| @@ -203,26 +209,20 @@ def match(argv1) next end - next unless r.match(diagnostic) + next unless r.match(issuedcode) reasontext = r.text break end return reasontext unless reasontext.empty? - typestring = '' - if cv = argv1.match(/\A(SMTP|X-.+);/i) - # Check the value of typestring - typestring = cv[1].upcase - end - - if typestring == 'X-UNIX' + if issuedcode.upcase == 'X-UNIX' # X-Unix; ... reasontext = 'mailererror' else # Detect the bounce reason from "Status:" code require 'sisimai/smtp/status' - statuscode = Sisimai::SMTP::Status.find(argv1) || '' - reasontext = Sisimai::SMTP::Status.name(statuscode) || 'undefined' + cv = Sisimai::SMTP::Status.find(argv1) || '' + reasontext = Sisimai::SMTP::Status.name(cv) || 'undefined' end return reasontext end From 1505db60c99b7e3d2076841f2be3f1deec273da1 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:44:02 +0900 Subject: [PATCH 30/37] Update for changes at Sisimai::Address.undisclosed --- test/public/address-test.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/public/address-test.rb b/test/public/address-test.rb index a8506172..32968d08 100644 --- a/test/public/address-test.rb +++ b/test/public/address-test.rb @@ -224,7 +224,7 @@ def test_is_mailerdaemon ce = assert_raises ArgumentError do Sisimai::Address.is_mailerdaemon() - Sisimai::Address.is_mailerdaemon(nil) + Sisimai::Address.is_mailerdaemon(nil, nil) end assert_match /wrong number of arguments/, ce.to_s end @@ -242,13 +242,13 @@ def test_undisclosed 'undisclosed-sender-in-headers@libsisimai.org.invalid', 'undisclosed-recipient-in-headers@libsisimai.org.invalid', ] - assert_equal ct[0], Sisimai::Address.undisclosed('s') - assert_equal ct[1], Sisimai::Address.undisclosed('r') - assert_nil Sisimai::Address.undisclosed('') + assert_equal ct[1], Sisimai::Address.undisclosed(1) + assert_equal ct[0], Sisimai::Address.undisclosed(nil) + assert_equal ct[1], Sisimai::Address.undisclosed("r") ce = assert_raises ArgumentError do - Sisimai::Address.undisclosed() - Sisimai::Address.undisclosed(nil) + Sisimai::Address.undisclosed(nil, nil) + Sisimai::Address.undisclosed(nil, nil, nil) end assert_match /wrong number of arguments/, ce.to_s end From 2172e50dc2b7837383100fe85ef861f3cd54520b Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:44:51 +0900 Subject: [PATCH 31/37] Reduce regular expressions --- lib/sisimai/rfc2045.rb | 2 +- lib/sisimai/rfc3464.rb | 38 +++++++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/sisimai/rfc2045.rb b/lib/sisimai/rfc2045.rb index 595e0cfd..23b5bf54 100644 --- a/lib/sisimai/rfc2045.rb +++ b/lib/sisimai/rfc2045.rb @@ -352,7 +352,7 @@ def makeflat(argv0 = '', argv1 = '') bodystring << bodyinside end - if mediatypev =~ %r + if mediatypev.include?('/delivery-status') || mediatypev.include?('/feedback-report') || mediatypev.include?('/rfc822') # Add Content-Type: header of each part (will be used as a delimiter at Sisimai::Lhost) # into the body inside when the value of Content-Type: field is message/delivery-status, # message/rfc822, or text/rfc822-headers diff --git a/lib/sisimai/rfc3464.rb b/lib/sisimai/rfc3464.rb index c788be02..15f4ca52 100644 --- a/lib/sisimai/rfc3464.rb +++ b/lib/sisimai/rfc3464.rb @@ -203,23 +203,29 @@ def inquire(mhead, mbody) end else # The line did not match with any fields defined in RFC3464 - if cv = e.match(/\ADiagnostic-Code:[ ]([^;]+)\z/) + if e.start_with?('Diagnostic-Code: ') && e.include?(';') == false # There is no value of "diagnostic-type" such as Diagnostic-Code: 554 ... - v['diagnosis'] = cv[1] - elsif cv = e.match(/\AStatus:[ ](\d{3}[ ]+.+)\z/) + v['diagnosis'] = e[e.index(' ') + 1, e.size] + + elsif e.start_with?('Status: ') && Sisimai::SMTP::Reply.find(e[8, 3]) # Status: 553 Exceeded maximum inbound message size - v['alterrors'] = cv[1] - elsif readslices[-2].start_with?('Diagnostic-Code:') && cv = e.match(/\A[ ]+(.+)\z/) + v['alterrors'] = e[8, e.size] + + elsif readslices[-2].start_with?('Diagnostic-Code:') && cv = e.start_with?(' ') # Continued line of the value of Diagnostic-Code header - v['diagnosis'] << ' ' << cv[1] + v['diagnosis'] << ' ' << e readslices[-1] = 'Diagnostic-Code: ' << e else # Get error messages which is written in the message body directly - next if e.start_with?(' ', ' ') - next unless e =~ /\A(?:[45]\d\d[ ]+|[<][^@]+[@][^@]+[>]:?[ ]+)/ - - v['alterrors'] ||= ' ' - v['alterrors'] << ' ' << e + next if e.start_with?(' ', ' ', 'X') + cr = Sisimai::SMTP::Reply.find(e) || '' + ca = Sisimai::Address.find(e) || [] + co = Sisimai::String.aligned(e, ['<', '@', '>']) + + if cr.size > 0 || (ca.size > 0 && co) + v['alterrors'] ||= ' ' + v['alterrors'] << ' ' << e + end end end end # End of if: rfc822 @@ -329,9 +335,9 @@ def inquire(mhead, mbody) recipients += 1 itisbounce ||= true - elsif cv = e.match(/[(](?:expanded|generated)[ ]from:?[ ]([^@]+[@][^@]+)[)]/) + elsif e.include?('(expanded from') || e.include?('(generated from') # (expanded from: neko@example.jp) - b['alias'] = Sisimai::Address.s3s4(cv[1]) + b['alias'] = Sisimai::Address.s3s4(e[e.rindex(' ') + 1, e.size]) end b['diagnosis'] ||= '' b['diagnosis'] << ' ' << e @@ -341,9 +347,11 @@ def inquire(mhead, mbody) end return nil unless itisbounce - if recipients == 0 && cv = rfc822text.match(/^To:[ ](.+)/) + p1 = rfc822text.index("\nTo: ") || -1 + p2 = rfc822text.index("\n", p1 + 6) || -1 + if recipients == 0 && p1 > 0 # Try to get a recipient address from "To:" header of the original message - if r = Sisimai::Address.find(cv[1], true) + if r = Sisimai::Address.find(rfc822text[p1 + 5, p2 - p1 - 5], true) # Found a recipient address dscontents << Sisimai::Lhost.DELIVERYSTATUS if dscontents.size == recipients b = dscontents[-1] From 7c0f353415bc071c65b6225ef988b910804e95a2 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:45:20 +0900 Subject: [PATCH 32/37] Update for renamed modules in Sisimai::Rhost::* --- test/public/rhost-engine-test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/rhost-engine-test.rb b/test/public/rhost-engine-test.rb index d23a533f..e0e22ac3 100644 --- a/test/public/rhost-engine-test.rb +++ b/test/public/rhost-engine-test.rb @@ -15,7 +15,7 @@ def test_rhostengine emailindex = ARGV[1] || 0 # % grep -h module lib/sisimai/rhost/*.rb | grep -vE '(Sisimai|Rhost)' | awk '{ print $2 }' - rhostindex = %w[Cox ExchangeOnline FrancePTT GoDaddy GoogleApps IUA KDDI Mimecast NTTDOCOMO Spectrum TencentQQ] + rhostindex = %w[Cox FrancePTT GoDaddy Google IUA KDDI Microsoft Mimecast NTTDOCOMO Spectrum Tencent] enginelist = [] enginename = '' From fe5a988546b34607e1dc88d82fc6389aae6a29cc Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:45:57 +0900 Subject: [PATCH 33/37] Fix an SMTP reply code in a test string --- test/public/smtp-error-test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/smtp-error-test.rb b/test/public/smtp-error-test.rb index 93684c01..758213bc 100644 --- a/test/public/smtp-error-test.rb +++ b/test/public/smtp-error-test.rb @@ -45,7 +45,7 @@ def test_soft_or_hard Bounces[:hard].each do |e| assert_equal 'hard', Sisimai::SMTP::Error.soft_or_hard(e) assert_equal 'hard', Sisimai::SMTP::Error.soft_or_hard(e, '503 Not accept any email') if e == 'notaccept' - assert_equal 'soft', Sisimai::SMTP::Error.soft_or_hard(e, '409 Not accept any email') if e == 'notaccept' + assert_equal 'soft', Sisimai::SMTP::Error.soft_or_hard(e, '458 Not accept any email') if e == 'notaccept' end NoError.each { |e| assert_equal '', Sisimai::SMTP::Error.soft_or_hard(e) } From 4b343a1340bd15982fa50fc45e9e31bd8559ba36 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:46:35 +0900 Subject: [PATCH 34/37] Add a test code for Sisimai::String.ipv4 --- test/public/string-test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/string-test.rb b/test/public/string-test.rb index aaec3670..c5e297a1 100644 --- a/test/public/string-test.rb +++ b/test/public/string-test.rb @@ -2,7 +2,7 @@ require 'sisimai/string' class StringTest < Minitest::Test - Methods = { class: %w[token is_8bit sweep aligned to_plain to_utf8] } + Methods = { class: %w[token is_8bit sweep aligned ipv4 to_plain to_utf8] } def test_methods Methods[:class].each { |e| assert_respond_to Sisimai::String, e } From e2c5aca68c0c547402bdeaf251899fff26626966 Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:47:13 +0900 Subject: [PATCH 35/37] Large scale improvement, Reduce regular expressions --- lib/sisimai/fact.rb | 160 +++++++++++-------- lib/sisimai/rhost/googleapps.rb | 265 -------------------------------- lib/sisimai/rhost/tencentqq.rb | 50 ------ 3 files changed, 98 insertions(+), 377 deletions(-) delete mode 100644 lib/sisimai/rhost/googleapps.rb delete mode 100644 lib/sisimai/rhost/tencentqq.rb diff --git a/lib/sisimai/fact.rb b/lib/sisimai/fact.rb index 6868d792..5953ec46 100644 --- a/lib/sisimai/fact.rb +++ b/lib/sisimai/fact.rb @@ -9,6 +9,7 @@ class Fact require 'sisimai/datetime' require 'sisimai/time' require 'sisimai/smtp/error' + require 'sisimai/smtp/command' require 'sisimai/string' require 'sisimai/rhost' @@ -43,7 +44,7 @@ class Fact RetryIndex = Sisimai::Reason.retry RFC822Head = Sisimai::RFC5322.HEADERFIELDS(:all) - ActionList = %r/\A(?:delayed|delivered|expanded|failed|relayed)\z/ + ActionList = { delayed: 1, delivered: 1, expanded: 1, failed: 1, relayed: 1 }; # Constructor of Sisimai::Fact # @param [Hash] argvs Including each parameter @@ -221,58 +222,89 @@ def self.rise(**argvs) p['subject'].chomp!("\r") if p['subject'].end_with?("\r") # The value of "List-Id" header - p['listid'] = rfc822data['list-id'] || '' - unless p['listid'].empty? - # Get the value of List-Id header like "List name " - if cv = p['listid'].match(/\A.*([<].+[>]).*\z/) then p['listid'] = cv[1] end - p['listid'].delete!('<>') - p['listid'].chomp!("\r") if p['listid'].end_with?("\r") - p['listid'] = '' if p['listid'].include?(' ') + if Sisimai::String.aligned(rfc822data['list-id'], ['<', '.', '>']) + # https://www.rfc-editor.org/rfc/rfc2919 + # Get the value of List-Id header: "List name " + p0 = rfc822data['list-id'].index('<') + 1 + p1 = rfc822data['list-id'].index('>') + p['listid'] = rfc822data['list-id'][p0, p1 - p0] + else + # Invalid value of the List-Id: field + p['listid'] = '' end # The value of "Message-Id" header - p['messageid'] = rfc822data['message-id'] || '' - unless p['messageid'].empty? + if Sisimai::String.aligned(rfc822data['message-id'], ['<', '@', '>']) + # https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4 # Leave only string inside of angle brackets(<>) - if cv = p['messageid'].match(/\A([^ ]+)[ ].*/) then p['messageid'] = cv[1] end - if cv = p['messageid'].match(/[<]([^ ]+?)[>]/) then p['messageid'] = cv[1] end + p0 = rfc822data['message-id'].index('<') + 1 + p1 = rfc822data['message-id'].index('>') + p['messageid'] = rfc822data['message-id'][p0, p1 - p0] + else + # Invalid value of the Message-Id: field + p['messageid'] = '' end # CHECK_DELIVERY_STATUS_VALUE: Cleanup the value of "Diagnostic-Code:" header - unless p['diagnosticcode'].empty? - # Count the number of D.S.N. and SMTP Reply Code - vm = 0 - vs = Sisimai::SMTP::Status.find(p['diagnosticcode']) - vr = Sisimai::SMTP::Reply.find(p['diagnosticcode']) - - if vs - # How many times does the D.S.N. appeared - vm += p['diagnosticcode'].scan(/\b#{vs}\b/).size - p['deliverystatus'] = vs if vs =~ /\A[45][.][1-9][.][1-9]+\z/ - end - - if vr - # How many times does the SMTP reply code appeared - vm += p['diagnosticcode'].scan(/\b#{vr}\b/).size - p['replycode'] = vr if p['replycode'].to_s.empty? + if p['diagnosticcode'].to_s.size > 0 + # Get an SMTP Reply Code and an SMTP Enhanced Status Code + p['diagnosticcode'].chop if p['diagnosticcode'][-1, 1] == "\r" + + cs = Sisimai::SMTP::Status.find(p['diagnosticcode']) || '' + cr = Sisimai::SMTP::Reply.find(p['diagnosticcode'], cs) || '' + p['deliverystatus'] = Sisimai::SMTP::Status.prefer(p['deliverystatus'], cs, cr) + + if cr.size == 3 + # There is an SMTP reply code in the error message + p['replycode'] = cr if p['replycode'].to_s.empty? + + if p['diagnosticcode'].include?(cr + '-') + # 550-5.7.1 [192.0.2.222] Our system has detected that this message is + # 550-5.7.1 likely unsolicited mail. To reduce the amount of spam sent to Gmail, + # 550-5.7.1 this message has been blocked. Please visit + # 550 5.7.1 https://support.google.com/mail/answer/188131 for more information. + # + # kijitora@example.co.uk + # host c.eu.example.com [192.0.2.3] + # SMTP error from remote mail server after end of data: + # 553-SPF (Sender Policy Framework) domain authentication + # 553-fail. Refer to the Troubleshooting page at + # 553-http://www.symanteccloud.com/troubleshooting for more + # 553 information. (#5.7.1) + ['-', " "].each do |q| + # Remove strings: "550-5.7.1", and "550 5.7.1" from the error message + cx = sprintf("%s%s%s", cr, q, cs) + p0 = p['diagnosticcode'].index(cx) + while p0 + # Remove strings like "550-5.7.1" + p['diagnosticcode'][p0, cx.size] = '' + p0 = p['diagnosticcode'].index(cx) + end + + # Remove "553-" and "553 " (SMTP reply code only) from the error message + cx = sprintf("%s%s", cr, q) + p0 = p['diagnosticcode'].index(cx) + while p0 + # Remove strings like "553-" + p['diagnosticcode'][p0, cx.size] = '' + p0 = p['diagnosticcode'].index(cx) + end + end + + if p['diagnosticcode'].index(cr).to_i > 1 + # Add "550 5.1.1" into the head of the error message when the error message does not + # begin with "550" + p['diagnosticcode'] = sprintf("%s %s %s", cr, cs, p['diagnosticcode']) + end + end end - if vm > 2 - # Build regular expression to remove a string like '550-5.1.1' from "diagnosticcode" - re0 = %r/;?[ ]#{vr}[- ](?:#{vs})?/ - re1 = %r/;?[ ][45]\d\d[- ](?:#{vs})?/ - re2 = %r/;?[ ]#{vr}[- ](?:[45][.]\d[.]\d+)?/ - - # 550-5.7.1 [192.0.2.222] Our system has detected that this message is - # 550-5.7.1 likely unsolicited mail. To reduce the amount of spam sent to Gmail, - # 550-5.7.1 this message has been blocked. Please visit - # 550 5.7.1 https://support.google.com/mail/answer/188131 for more information. - p['diagnosticcode'] = p['diagnosticcode'].gsub(re0, ' ') - p['diagnosticcode'] = p['diagnosticcode'].gsub(re1, ' ') - p['diagnosticcode'] = p['diagnosticcode'].gsub(re2, ' ') - p['diagnosticcode'] = Sisimai::String.sweep(p['diagnosticcode'].sub(%r|.+|i, '')) - end + p1 = p['diagnosticcode'].downcase.index('') + p2 = p['diagnosticcode'].downcase.index('') + p['diagnosticcode'][p1, p2 + 7 - p1] = '' if p1 && p2 + p['diagnosticcode'] = Sisimai::String.sweep(p['diagnosticcode']) end + if Sisimai::String.is_8bit(p['diagnosticcode']) # To avoid incompatible character encodings: ASCII-8BIT and UTF-8 (Encoding::CompatibilityError p['diagnosticcode'] = p['diagnosticcode'].force_encoding('UTF-8').scrub('?') @@ -283,7 +315,7 @@ def self.rise(**argvs) p['diagnostictype'] ||= 'SMTP' unless %w[feedback vacation].include?(p['reason']) # Check the value of SMTP command - p['smtpcommand'] = '' unless %w[CONN EHLO HELO STARTTLS AUTH MAIL RCPT DATA QUIT].include?(p['smtpcommand']) + p['smtpcommand'] = '' unless Sisimai::SMTP::Command.test(p['smtpcommand']) # Create parameters for the constructor as = Sisimai::Address.new(p['addresser']) || next; next if as.void @@ -313,24 +345,24 @@ def self.rise(**argvs) # REASON: Decide the reason of email bounce if o['reason'].empty? || RetryIndex[o['reason']] # The value of "reason" is empty or is needed to check with other values again - de = o['destination']; r = '' - r = Sisimai::Rhost.get(o) if Sisimai::Rhost.match(o['rhost']) - if r.empty? + 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" - r = Sisimai::Rhost.get(o, de) if Sisimai::Rhost.match(de) - r = Sisimai::Reason.get(o) if r.empty? - r = 'undefined' if r.empty? + re = Sisimai::Rhost.get(o, de) if Sisimai::Rhost.match(de) + re = Sisimai::Reason.get(o) if re.empty? + re = 'undefined' if re.empty? end - o['reason'] = r + o['reason'] = re end # HARDBOUNCE: Set the value of "hardbounce", default value of "bouncebounce" is false - if o['reason'] =~ /\A(?:delivered|feedback|vacation)\z/ + if o['reason'] == 'delivered' || o['reason'] == 'feedback' || o['reason'] == 'vacation' # The value of "reason" is "delivered", "vacation" or "feedback". o['replycode'] = '' unless o['reason'] == 'delivered' else smtperrors = p['deliverystatus'] + ' ' << p['diagnosticcode'] - smtperrors = '' if smtperrors =~ /\A\s+\z/ + smtperrors = '' if smtperrors.size < 4 softorhard = Sisimai::SMTP::Error.soft_or_hard(o['reason'], smtperrors) o['hardbounce'] = true if softorhard == 'hard' end @@ -338,18 +370,22 @@ def self.rise(**argvs) # DELIVERYSTATUS: Set a pseudo status code if the value of "deliverystatus" is empty if o['deliverystatus'].empty? smtperrors = p['replycode'] + ' ' << p['diagnosticcode'] - smtperrors = '' if smtperrors =~ /\A\s+\z/ + smtperrors = '' if smtperrors.size < 4 permanent1 = Sisimai::SMTP::Error.is_permanent(smtperrors) permanent1 = true if permanent1 == nil o['deliverystatus'] = Sisimai::SMTP::Status.code(o['reason'], permanent1 ? false : true) || '' end # REPLYCODE: Check both of the first digit of "deliverystatus" and "replycode" - d1 = o['deliverystatus'][0, 1] - r1 = o['replycode'][0, 1] - o['replycode'] = '' unless d1 == r1 + cx = [o['deliverystatus'][0, 1], o['replycode'][0, 1]] + if cx[0] != cx[1] + # The class of the "Status:" is defer with the first digit of the reply code + cx[1] = Sisimai::SMTP::Reply.find(p['diagnosticcode'], cx[0]) || '' + o['replycode'] = cx[1].start_with?(cx[0]) ? cx[1] : '' + end - unless o['action'] =~ ActionList + unless ActionList.has_key?(o['action']) + # There is an action value which is not described at RFC1894 if ox = Sisimai::RFC1894.field('Action: ' << o['action']) # Rewrite the value of "Action:" field to the valid value # @@ -361,7 +397,7 @@ def self.rise(**argvs) end o['action'] = 'delayed' if o['reason'] == 'expired' if o['action'].empty? - o['action'] = 'failed' if d1.start_with?('4', '5') + o['action'] = 'failed' if cx[0] == '4' || cx[0] == '5' end listoffact << Sisimai::Fact.new(o) @@ -373,9 +409,9 @@ def self.rise(**argvs) # @return [Integer] def softbounce warn ' ***warning: Sisimai::Fact.softbounce will be removed at v5.1.0. Use Sisimai::Fact.hardbounce instead' - return 0 if self.hardbounce - return -1 if self.reason =~ /\A(?:delivered|feedback|vacation)\z/ - return 1 + return 0 if self.hardbounce + return -1 if self.reason == 'delivered' || self.reason == 'feedback' || self.reason == 'vacation' + return 1 end # Convert from Sisimai::Fact object to a Hash diff --git a/lib/sisimai/rhost/googleapps.rb b/lib/sisimai/rhost/googleapps.rb deleted file mode 100644 index 8de40b65..00000000 --- a/lib/sisimai/rhost/googleapps.rb +++ /dev/null @@ -1,265 +0,0 @@ -module Sisimai - module Rhost - # Sisimai::Rhost detects the bounce reason from the content of Sisimai::Fact object as an argument - # of get() method when the value of "rhost" of the object is "aspmx.l.google.com". This class is - # called only Sisimai::Fact class. - module GoogleApps - class << self - MessagesOf = { - 'authfailure' => [ - # - 550 5.7.26 Unauthenticated email from domain-name is not accepted due to domain's - # DMARC policy. Please contact the administrator of domain-name domain. If this was - # a legitimate mail please visit [Control unauthenticated mail from your domain] to - # learn about the DMARC initiative. If the messages are valid and aren't spam, con- - # tact the administrator of the receiving mail server to determine why your outgoing - # messages don't pass authentication checks. - ['550', '5.7.26', "is not accepted due to domain's dmarc policy"], - - # - 550 5.7.26 This message does not have authentication information or fails to pass - # authentication checks (SPF or DKIM). To best protect our users from spam, the mes- - # sage has been blocked. Please visit https://support.google.com/mail/answer/81126 - # for more information. - ['550', '5.7.1', 'fails to pass authentication checks'], - ['550', '5.7.26', 'fails to pass authentication checks'], - - # - 550 5.7.26 This message fails to pass SPF checks for an SPF record with a hard fail - # policy (-all). To best protect our users from spam and phishing, the message has - # been blocked. Please visit https://support.google.com/mail/answer/81126 for more - # information. - ['550', '5.7.26', 'this message fails to pass spf checks for an spf record with a hard fail'], - ], - 'badreputation' => [ - # - 550 5.7.1 Our system has detected that this message is likely suspicious due to the - # very low reputation of the sending IP address. To best protect our users from spam, - # the message has been blocked. - # Please visit https://support.google.com/mail/answer/188131 for more information. - ['550', '5.7.1', 'this message is likely suspicious due to the very low reputation of the sending ip address'], - ], - 'blocked' => [ - ['421', '4.7.0', 'ip not in whitelist for rcpt domain, closing connection.'], - - # - 421 4.7.0 Our system has detected an unusual rate of unsolicited mail originating - # from your IP address. To protect our users from spam, mail sent from your IP ad- - # dress has been temporarily blocked. - # For more information, visit https://support.google.com/mail/answer/81126 - ['421', '4.7.0', 'our system has detected an unusual rate of unsolicited mail originating from your ip address'], - - # - 421 4.7.0 Try again later, closing connection. This usually indicates a Denial of - # Service (DoS) for the SMTP relay at the HELO stage. - ['421', '4.7.0', 'try again later, closing connection.'], - - # - 501 5.5.4 HELO/EHLO argument is invalid. For more information, visit [HELO/EHLO e- - # mail error]. - ['501', '5.5.4', 'helo/ehlo argument is invalid'], - - # - 550 5.7.1 Our system has detected an unusual rate of unsolicited mail originating - # from your IP address. To protect our users from spam, mail sent from your IP ad- - # dress has been blocked. Review https://support.google.com/mail/answer/81126 - ['550', '5.7.1', 'our system has detected an unusual rate of unsolicited mail originating from your ip address'], - - # - 550 5.7.1 The IP you're using to send mail is not authorized to send email directly - # to our servers. Please use the SMTP relay at your service provider instead. For - # more information, visit https://support.google.com/mail/answer/10336 - ['550', '5.7.1', "the ip you're using to send mail is not authorized to send email directly to our servers"], - - # - 550 5.7.25 The IP address sending this message does not have a PTR record setup, or - # the corresponding forward DNS entry does not point to the sending IP. As a policy, - # Gmail does not accept messages from IPs with missing PTR records. - ['550', '5.7.25', 'the ip address sending this message does not have a ptr record setup'], - ], - 'contenterror' => [ - ['554', '5.6.0', 'mail message is malformed. Not accepted'], - ], - 'exceedlimit' => [ - # - 552 5.2.3 Your message exceeded Google's message size limits. For more information, - # visit https://support.google.com/mail/answer/6584 - ['552', '5.2.3', "your message exceeded Google's message size limits"], - ], - 'expired' => [ - ['451', '4.4.2', 'timeout - closing connection'], - ], - 'mailboxfull' => [ - # - 452 4.2.2 The email account that you tried to reach is over quota. Please direct - # the recipient to Clear Google Drive space & increase storage. - ['452', '4.2.2', 'the email account that you tried to reach is over quota'], - ['552', '5.2.2', 'the email account that you tried to reach is over quota'], - ['550', '5.7.1', 'email quota exceeded'], - ], - 'networkerror' => [ - ['554', '5.6.0', 'message exceeded 50 hops, this may indicate a mail loop'], - ], - 'norelaying' => [ - ['550', '5.7.0', 'mail relay denied'], - ], - 'policyviolation' => [ - ['550', '5.7.1', 'messages with multiple addresses in from: header are not accepted'], - - # - 550 5.7.1 The user or domain that you are sending to (or from) has a policy that - # prohibited the mail that you sent. Please contact your domain administrator for - # further details. - # For more information, visit https://support.google.com/a/answer/172179 - ['550', '5.7.1', 'the user or domain that you are sending to (or from) has a policy that prohibited'], - - # - 552 5.7.0 Our system detected an illegal attachment on your message. Please visit - # http://mail.google.com/support/bin/answer.py?answer=6590 to review our attachment - # guidelines. - ['552', '5.7.0', 'our system detected an illegal attachment on your message'], - - # - 552 5.7.0 This message was blocked because its content presents a potential securi- - # ty issue. Please visit https://support.google.com/mail/?p=BlockedMessage to review - # our message content and attachment content guidelines. - ['552', '5.7.0', 'this message was blocked because its content presents a potential security issue'], - ], - 'rejected' => [ - # - 550 5.7.0, Mail Sending denied. This error occurs if the sender account is disabled - # or not registered within your Google Workspace domain. - ['550', '5.7.0', 'mail sending denied'], - - ['550', '5.7.1', 'unauthenticated email is not accepted from this domain'], - ], - 'securityerror' => [ - ['421', '4.7.0', 'tls required for rcpt domain, closing connection'], - ['501', '5.5.2', 'cannot decode response'], # 2FA related error, maybe. - - # - 530 5.5.1 Authentication Required. For more information, visit - # https://support.google.com/accounts/troubleshooter/2402620 - ['530', '5.5.1', 'authentication required.'], - - # - 535 5.7.1 Application-specific password required. - # For more information, visit https://support.google.com/accounts/answer/185833 - ['535', '5.7.1', 'application-specific password required'], - - # - 535 5.7.1 Please log in with your web browser and then try again. For more infor- - # mation, visit https://support.google.com/mail/bin/accounts/answer/78754 - ['535', '5.7.1', 'please log in with your web browser and then try again'], - - # - 535 5.7.1 Username and Password not accepted. For more information, visit - # https://support.google.com/accounts/troubleshooter/2402620 - ['535', '5.7.1', 'username and password not accepted'], - - ['550', '5.7.1', 'invalid credentials for relay'], - ], - 'spamdetected' => [ - # - 550 5.7.1 Our system has detected that this message is likely unsolicited mail. To - # reduce the amount of spam sent to Gmail, this message has been blocked. - # For more information, visit https://support.google.com/mail/answer/188131 - ['550', '5.7.1', 'our system has detected that this message is likely unsolicited mail'], - ], - 'speeding' => [ - # - 450 4.2.1 The user you are trying to contact is receiving mail too quickly. Please - # resend your message at a later time. If the user is able to receive mail at that - # time, your message will be delivered. - # For more information, visit https://support.google.com/mail/answer/22839 - ['450', '4.2.1', 'is receiving mail too quickly'], - - # - 450 4.2.1 The user you are trying to contact is receiving mail at a rate that pre- - # vents additional messages from being delivered. Please resend your message at a - # later time. If the user is able to receive mail at that time, your message will be - # delivered. For more information, visit https://support.google.com/mail/answer/6592 - ['450', '4.2.1', 'is receiving mail at a rate that prevents additional messages from being delivered'], - ['550', '5.2.1', 'is receiving mail at a rate that prevents additional messages from being delivered'], - - # - 450 4.2.1 Peak SMTP relay limit exceeded for customer. This is a temporary error. - # For more information on SMTP relay limits, please contact your administrator or - # visit https://support.google.com/a/answer/6140680 - ['450', '4.2.1', 'peak smtp relay limit exceeded for customer'], - - # - 550 5.4.5 Daily SMTP relay limit exceeded for user. For more information on SMTP - # relay sending limits please contact your administrator or visit SMTP relay service - # error messages. - ['550', '5.4.5', 'daily smtp relay limit exceeded for user'], - ['550', '5.4.5', 'daily sending quota exceeded'], - - # - 550 5.7.1 Daily SMTP relay limit exceeded for customer. For more information on - # SMTP relay sending limits please contact your administrator or visit - # https://support.google.com/a/answer/6140680 - ['550', '5.7.1', 'daily smtp relay limit exceeded for customer'], - ], - 'suspend' => [ - ['550', '5.2.1', 'the email account that you tried to reach is disabled'], - ], - 'syntaxerror' => [ - ['451', '4.5.0', 'smtp protocol violation, visit rfc 2821'], - ['454', '4.5.0', 'smtp protocol violation, no commands allowed to pipeline after starttls'], - ['454', '5.5.1', 'starttls may not be repeated'], - ['502', '5.5.1', 'too many unrecognized commands, goodbye'], - ['502', '5.5.1', 'unimplemented command'], - ['502', '5.5.1', 'unrecognized command'], - ['503', '5.5.1', 'ehlo/helo first'], - ['503', '5.5.1', 'mail first'], - ['503', '5.5.1', 'rcpt first'], - ['503', '5.7.0', 'no identity changes permitted'], - ['504', '5.7.4', 'unrecognized authentication type'], - ['530', '5.7.0', 'must issue a starttls command first'], - ['535', '5.5.4', 'optional argument not permitted for that auth mode'], - ['554', '5.7.0', 'too many unauthenticated commands'], - ['555', '5.5.2', 'syntax error'], - ], - 'systemerror' => [ - ['421', '4.3.0', 'temporary system problem'], - ['421', '4.7.0', 'temporary system problem'], - ['421', '4.4.5', 'server busy'], - ['451', '4.3.0', 'mail server temporarily rejected message'], - ['454', '4.7.0', 'cannot authenticate due to temporary system problem'], - - # - 452 4.5.3 Domain policy size per transaction exceeded, please try this recipient in - # a separate transaction. This message means the email policy size (size of policies, - # number of policies, or both) for the recipient domain has been exceeded. - ['452', '4.5.3', 'domain policy size per transaction exceeded'], - ], - 'toomanyconn' => [ - ['451', '4.3.0', 'multiple destination domains per transaction is unsupported'], - - # - 452 4.5.3 Your message has too many recipients. For more information regarding - # Google's sending limits, visit https://support.google.com/mail/answer/6592 - ['452', '4.5.3', 'your message has too many recipients'], - ], - 'userunknown' => [ - # - 550 5.1.1 The email account that you tried to reach does not exist. Please try dou- - # ble-checking the recipient's email address for typos or unnecessary spaces. - # For more information, visit https://support.google.com/mail/answer/6596 - ['550', '5.1.1', 'the email account that you tried to reach does not exist'], - - # - 553 5.1.2 We weren't able to find the recipient domain. Please check for any spell- - # ing errors, and make sure you didn't enter any spaces, periods, or other punctua- - # tion after the recipient's email address. - ['553', '5.1.2', "we weren't able to find the recipient domain"], - ], - }.freeze - - # Detect bounce reason from Google Workspace - # @param [Sisimai::Fact] argvs Parsed email object - # @return [String] The bounce reason for Google Workspace - # @see https://support.google.com/a/answer/3726730?hl=en - def get(argvs) - return argvs['reason'] unless argvs['reason'].empty? - return '' if argvs['replycode'].empty? - return '' if argvs['diagnosticcode'].empty? - return '' if argvs['deliverystatus'].empty? - return '' unless argvs['deliverystatus'] =~ /\A[245][.]\d[.]\d+\z/ - - statuscode = argvs['deliverystatus'][2,6] - esmtpreply = argvs['replycode'][1,2] - esmtperror = argvs['diagnosticcode'].downcase - reasontext = '' - - MessagesOf.each_key do |e| - # Each key is a reason name - MessagesOf[e].each do |f| - # Try to match an SMTP reply code, a D.S.N, and an error message - next unless esmtperror.include?(f[2]) - next unless f[0].end_with?(esmtpreply) - next unless f[1].end_with?(statuscode) - reasontext = e - break - end - break unless reasontext.empty? - end - - return reasontext - end - - end - end - end -end diff --git a/lib/sisimai/rhost/tencentqq.rb b/lib/sisimai/rhost/tencentqq.rb deleted file mode 100644 index e77e5c57..00000000 --- a/lib/sisimai/rhost/tencentqq.rb +++ /dev/null @@ -1,50 +0,0 @@ -module Sisimai - module Rhost - # Sisimai::Rhost detects the bounce reason from the content of Sisimai::Fact object as an argument - # of get() method when the value of "rhost" of the object is "mx*.qq.com". This class is called - # only Sisimai::Fact class. - module TencentQQ - class << self - MessagesOf = { - # https://service.mail.qq.com/cgi-bin/help?id=20022 - 'dmarc check failed' => 'blocked', - 'spf check failed' => 'blocked', - 'suspected spam ip' => 'blocked', - 'mail is rejected by recipients' => 'filtered', - 'message too large' => 'mesgtoobig', - 'mail content denied' => 'spamdetected', - 'spam is embedded in the email' => 'spamdetected', - 'suspected spam' => 'spamdetected', - 'bad address syntax' => 'syntaxerror', - 'connection denied' => 'toomanyconn', - 'connection frequency limited' => 'toomanyconn', - 'domain frequency limited' => 'toomanyconn', - 'ip frequency limited' => 'toomanyconn', - 'sender frequency limited' => 'toomanyconn', - 'mailbox unavailable or access denied' => 'toomanyconn', - 'mailbox not found' => 'userunknown', - }.freeze - - # Detect bounce reason from Tencent QQ - # @param [Sisimai::Fact] argvs Parsed email object - # @return [String] The bounce reason at Tencent QQ - def get(argvs) - return argvs['reason'] unless argvs['reason'].empty? - - statusmesg = argvs['diagnosticcode'].downcase - reasontext = '' - - MessagesOf.each_key do |e| - # Try to match the error message with message patterns defined in $MessagesOf - next unless statusmesg.include?(e) - reasontext = MessagesOf[e] - break - end - return reasontext - end - - end - end - end -end - From 64cc4e7e91b5c72e5533b4895c1ec3fe7ea9b47a Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:47:35 +0900 Subject: [PATCH 36/37] Update test codes --- test/private/rfc3464.rb | 16 ++++++++-------- test/public/mda-test.rb | 4 ---- test/public/rfc3464.rb | 2 +- test/public/rhost-googleapps.rb | 10 ---------- test/public/rhost-tencentqq.rb | 11 ----------- 5 files changed, 9 insertions(+), 34 deletions(-) delete mode 100644 test/public/rhost-googleapps.rb delete mode 100644 test/public/rhost-tencentqq.rb diff --git a/test/private/rfc3464.rb b/test/private/rfc3464.rb index 5072a4ed..1df1823a 100644 --- a/test/private/rfc3464.rb +++ b/test/private/rfc3464.rb @@ -11,7 +11,7 @@ module RFC3464 '01008' => [['5.0.947', '', 'expired', false]], '01009' => [['5.1.1', '550', 'userunknown', true]], '01011' => [['5.1.2', '550', 'hostunknown', true]], - '01013' => [['5.0.0', '550', 'filtered', false]], + '01013' => [['5.1.0', '550', 'userunknown', true]], '01014' => [['5.1.1', '550', 'userunknown', true]], '01015' => [['5.0.912', '', 'hostunknown', true]], '01016' => [['5.1.1', '', 'userunknown', true]], @@ -80,9 +80,9 @@ module RFC3464 '01091' => [['5.0.900', '', 'undefined', false]], '01092' => [['5.0.900', '', 'undefined', false]], '01093' => [['5.2.0', '', 'filtered', false]], - '01095' => [['5.0.0', '550', 'filtered', false]], + '01095' => [['5.1.0', '550', 'userunknown', true]], '01096' => [['5.2.0', '', 'filtered', false]], - '01097' => [['5.0.0', '550', 'filtered', false]], + '01097' => [['5.1.0', '550', 'userunknown', true]], '01098' => [['5.2.0', '', 'filtered', false]], '01099' => [['4.7.0', '', 'securityerror', false]], '01100' => [['4.7.0', '', 'securityerror', false]], @@ -95,7 +95,7 @@ module RFC3464 '01107' => [['5.2.0', '', 'filtered', false]], '01108' => [['5.0.900', '', 'undefined', false]], '01111' => [['5.0.922', '', 'mailboxfull', false]], - '01112' => [['5.0.0', '550', 'filtered', false]], + '01112' => [['5.1.0', '550', 'userunknown', true]], '01113' => [['5.2.0', '', 'filtered', false]], '01114' => [['5.0.930', '', 'systemerror', false]], '01117' => [['5.0.934', '553', 'mesgtoobig', false]], @@ -106,7 +106,7 @@ module RFC3464 '01123' => [['4.4.1', '', 'expired', false]], '01124' => [['4.0.0', '', 'mailererror', false]], '01125' => [['5.0.944', '', 'networkerror', false]], - '01126' => [['5.1.1', '554', 'userunknown', true]], + '01126' => [['5.1.1', '550', 'userunknown', true]], '01127' => [['5.2.0', '', 'filtered', false]], '01128' => [['5.0.930', '', 'systemerror', false], ['5.0.901', '', 'onhold', false]], @@ -187,7 +187,7 @@ module RFC3464 '01220' => [['5.2.0', '', 'filtered', false]], '01222' => [['5.2.2', '552', 'mailboxfull', false]], '01223' => [['4.0.0', '', 'mailboxfull', false]], - '01224' => [['5.5.1', '553', 'authfailure', false]], + '01224' => [['5.1.1', '550', 'authfailure', false]], '01225' => [['4.4.7', '', 'expired', false]], '01227' => [['5.5.0', '', 'userunknown', true], ['5.5.0', '', 'userunknown', true]], @@ -239,7 +239,7 @@ module RFC3464 '01277' => [['5.0.0', '550', 'rejected', false], ['4.0.0', '', 'expired', false], ['5.0.0', '550', 'filtered', false]], - '01278' => [['4.0.0', '426', 'expired', false]], + '01278' => [['4.0.0', '', 'expired', false]], '01279' => [['4.4.6', '', 'networkerror', false]], '01280' => [['5.4.0', '', 'networkerror', false]], '01282' => [['5.1.1', '550', 'userunknown', true]], @@ -248,7 +248,7 @@ module RFC3464 '01285' => [['5.7.0', '554', 'spamdetected', false]], '01286' => [['5.5.0', '550', 'rejected', false]], '01287' => [['5.0.0', '550', 'filtered', false]], - '01288' => [['5.0.0', '552', 'exceedlimit', false]], + '01288' => [['5.3.0', '552', 'exceedlimit', false]], '01289' => [['4.0.0', '', 'notaccept', false]], '01290' => [['4.3.0', '451', 'onhold', false]], } diff --git a/test/public/mda-test.rb b/test/public/mda-test.rb index 65f7eb85..1a11694c 100644 --- a/test/public/mda-test.rb +++ b/test/public/mda-test.rb @@ -45,10 +45,6 @@ def test_inquire Sisimai::MDA.inquire(nil, nil, nil) end - ce = assert_raises NoMethodError do - Sisimai::MDA.inquire(nil, nil) - end - end end diff --git a/test/public/rfc3464.rb b/test/public/rfc3464.rb index 5f2028b0..a42f0a62 100644 --- a/test/public/rfc3464.rb +++ b/test/public/rfc3464.rb @@ -18,7 +18,7 @@ module RFC3464 '35' => [['5.0.0', '550', 'rejected', false], ['4.0.0', '', 'expired', false], ['5.0.0', '550', 'filtered', false]], - '36' => [['4.0.0', '426', 'expired', false]], + '36' => [['4.0.0', '', 'expired', false]], '37' => [['5.0.912', '', 'hostunknown', true]], '38' => [['5.0.922', '', 'mailboxfull', false]], '39' => [['5.0.901', '', 'onhold', false]], diff --git a/test/public/rhost-googleapps.rb b/test/public/rhost-googleapps.rb deleted file mode 100644 index 6c572d02..00000000 --- a/test/public/rhost-googleapps.rb +++ /dev/null @@ -1,10 +0,0 @@ -module RhostEngineTest::Public - module GoogleApps - IsExpected = { - # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] - '01' => [['5.2.1', '550', 'suspend', false]], - '02' => [['5.1.1', '550', 'userunknown', true]], - } - end -end - diff --git a/test/public/rhost-tencentqq.rb b/test/public/rhost-tencentqq.rb deleted file mode 100644 index db3d35e6..00000000 --- a/test/public/rhost-tencentqq.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RhostEngineTest::Public - module TencentQQ - IsExpected = { - # INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce'], [...]] - '01' => [['5.0.0', '550', 'toomanyconn', false]], - '02' => [['5.0.0', '550', 'toomanyconn', false]], - '03' => [['5.0.0', '550', 'blocked', false]], - } - end -end - From 915b9a4316b84c1ca5cf8e14399fc1a0c517a73d Mon Sep 17 00:00:00 2001 From: azumakuniyuki Date: Tue, 9 May 2023 08:47:47 +0900 Subject: [PATCH 37/37] Remove renamed email files --- .../maildir/bsd/rhost-exchangeonline-01.eml | 73 ------- .../maildir/bsd/rhost-exchangeonline-02.eml | 89 -------- .../maildir/bsd/rhost-exchangeonline-03.eml | 205 ------------------ .../maildir/bsd/rhost-googleapps-01.eml | 66 ------ .../maildir/bsd/rhost-googleapps-02.eml | 88 -------- .../maildir/bsd/rhost-tencentqq-01.eml | 84 ------- .../maildir/bsd/rhost-tencentqq-02.eml | 84 ------- .../maildir/bsd/rhost-tencentqq-03.eml | 81 ------- 8 files changed, 770 deletions(-) delete mode 100644 set-of-emails/maildir/bsd/rhost-exchangeonline-01.eml delete mode 100644 set-of-emails/maildir/bsd/rhost-exchangeonline-02.eml delete mode 100644 set-of-emails/maildir/bsd/rhost-exchangeonline-03.eml delete mode 100644 set-of-emails/maildir/bsd/rhost-googleapps-01.eml delete mode 100644 set-of-emails/maildir/bsd/rhost-googleapps-02.eml delete mode 100644 set-of-emails/maildir/bsd/rhost-tencentqq-01.eml delete mode 100644 set-of-emails/maildir/bsd/rhost-tencentqq-02.eml delete mode 100644 set-of-emails/maildir/bsd/rhost-tencentqq-03.eml diff --git a/set-of-emails/maildir/bsd/rhost-exchangeonline-01.eml b/set-of-emails/maildir/bsd/rhost-exchangeonline-01.eml deleted file mode 100644 index 038996f8..00000000 --- a/set-of-emails/maildir/bsd/rhost-exchangeonline-01.eml +++ /dev/null @@ -1,73 +0,0 @@ -Return-Path: <> -Received: from nyaan.example.ed.jp (nyaan.example.ed.jp [198.51.100.222]) - by aneyakoji.neko.example.co.jp (V8/cf) with ESMTP id t3BB0AAA00000 - for ; Thu, 29 Apr 2015 20:00:00 +0900 -X-SenderID: Sendmail Sender-ID Filter v1.0.0 aneyakoji.neko.example.co.jp t3BB0AAA00000 -Authentication-Results: aneyakoji.neko.example.co.jp; sender-id=none header.from=MAILER-DAEMON@nyaan.example.ed.jp -Received: from localhost (localhost) - by nyaan.example.ed.jp (V8/cf) id t3BB0AAA11111; - Thu, 29 Apr 2015 20:00:00 +0900 -Date: Thu, 29 Apr 2015 20:00:00 +0900 -From: Mail Delivery Subsystem -Message-Id: <201505111100.t3BB0AAA11111@nyaan.example.ed.jp> -To: postmaster@nyaan.example.ed.jp -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="t3BB0AAA11111.1400000000/nyaan.example.ed.jp" -Subject: Postmaster notify: see transcript for details -Auto-Submitted: auto-generated (postmaster-notification) - -This is a MIME-encapsulated message - ---t3BB0AAA11111.1400000000/nyaan.example.ed.jp - -The original message was received at Thu, 29 Apr 2015 20:00:00 +0900 -from localhost [127.0.0.1] -with id t3BB0AAA22222 - - ----- The following addresses had permanent fatal errors ----- - - (reason: 550 5.7.606 Access denied, banned sending IP [198.51.100.222]. To request removal from this list plea...tions. For more information please go to http://go.microsoft.com/fwlink/?LinkID=526655 (AS16012609)) - - ----- Transcript of session follows ----- -... while talking to example.com.mail.protection.outlook.com.: ->>> DATA -<<< 550 5.7.606 Access denied, banned sending IP [198.51.100.222]. To request removal from this list please visit https://sender.office.com/ and follow the directions. For more information please go to http://go.microsoft.com/fwlink/?LinkID=526655 (AS16012609) -550 5.1.1 ... User unknown -<<< 503 5.5.2 Need rcpt command - ---t3BB0AAA11111.1400000000/nyaan.example.ed.jp -Content-Type: message/delivery-status - -Reporting-MTA: dns; nyaan.example.ed.jp -Received-From-MTA: DNS; localhost -Arrival-Date: Thu, 29 Apr 2015 20:00:00 +0900 - -Final-Recipient: RFC822; kijitora@example.com -Action: failed -Status: 5.7.606 -Remote-MTA: DNS; example.com.mail.protection.outlook.com -Diagnostic-Code: SMTP; 550 5.7.606 Access denied, banned sending IP [198.51.100.222]. To request removal from this list please visit https://sender.office.com/ and follow the directions. For more information please go to http://go.microsoft.com/fwlink/?LinkID=526655 (AS16012609) -Last-Attempt-Date: Thu, 29 Apr 2015 20:00:00 +0900 - ---t3BB0AAA11111.1400000000/nyaan.example.ed.jp -Content-Type: text/rfc822-headers - -Return-Path: -Received: from nyaan.example.ed.jp (localhost [127.0.0.1]) - by nyaan.example.ed.jp (V8/cf) with ESMTP id t3BB0AAA22222 - for ; Thu, 29 Apr 2015 20:00:00 +0900 -Received: (from root@localhost) - by nyaan.example.ed.jp (8.15.2/8.15.2/Submit) id t3BB0AAA23232; - Thu, 29 Apr 2015 20:00:00 +0900 -Date: Thu, 29 Apr 2015 20:00:00 +0900 -To: kijitora@example.com -Subject: Nyaan -From: Shironeko -Message-Id: <00000000000000000000000000000000000000000000000@example.net> -Content-Type: text/plain; charset="iso-2022-jp" -Content-Transfer-Encoding: 7bit -MIME-Version: 1.0 - ---t3BB0AAA11111.1400000000/nyaan.example.ed.jp-- - diff --git a/set-of-emails/maildir/bsd/rhost-exchangeonline-02.eml b/set-of-emails/maildir/bsd/rhost-exchangeonline-02.eml deleted file mode 100644 index d30a2545..00000000 --- a/set-of-emails/maildir/bsd/rhost-exchangeonline-02.eml +++ /dev/null @@ -1,89 +0,0 @@ -Return-Path: -Received: from email110.example.com (unknown [192.0.2.222]) - by s151v.example.com (Bossmail) with ESMTP id FFF222EEE22 - for ; Thu, 29 Apr 2017 23:34:45 +0900 (JST) -Received: from email2.example.com ([192.0.2.22]) - by email110.example.com with ESMTP/TLS/DHE-RSA-AES256-SHA; 29 Apr 2017 23:34:45 +0900 -Received: by email2.example.com (Postfix) - id EEEEEEEEEE2; Thu, 29 Apr 2017 23:34:45 +0900 (JST) -Date: Thu, 29 Apr 2017 23:34:45 +0900 (JST) -From: MAILER-DAEMON@email2.example.com (Mail Delivery System) -Subject: Undelivered Mail Returned to Sender -To: sironeko@example.co.jp -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="2222FFFFEEEE.0000000000/email2.example.com" -Content-Transfer-Encoding: 8bit -Message-Id: <20170429233445.00000000000@email2.example.com> - -This is a MIME-encapsulated message. - ---2222FFFFEEEE.0000000000/email2.example.com -Content-Description: Notification -Content-Type: text/plain; charset=us-ascii - -This is the mail system at host email2.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 - -: host - example.org.mail.protection.outlook.com[198.51.100.2] said: 550 - 5.4.1 [kijitora@example.org]: Recipient address rejected: Access - denied [NEKOCHAN.cat-neko22.prod.protection.outlook.com] (in reply to - RCPT TO command) - ---2222FFFFEEEE.0000000000/email2.example.com -Content-Description: Delivery report -Content-Type: message/delivery-status - -Reporting-MTA: dns; email2.example.com -X-Postfix-Queue-ID: 00000000000 -X-Postfix-Sender: rfc822; sironeko@example.co.jp -Arrival-Date: Thu, 29 Apr 2017 23:34:45 +0900 (JST) - -Final-Recipient: rfc822; kijitora@example.org -Original-Recipient: rfc822;kijitora@example.org -Action: failed -Status: 5.4.1 -Remote-MTA: dns; example.org.mail.protection.outlook.com -Diagnostic-Code: smtp; 550 5.4.1 [kijitora@example.org]: Recipient - address rejected: Access denied - [NEKOCHAN.cat-neko22.prod.protection.outlook.com] - ---2222FFFFEEEE.0000000000/email2.example.com -Content-Description: Undelivered Message -Content-Type: message/rfc822 -Content-Transfer-Encoding: 8bit - -Received: from smtp2.example.com (smtp2.example.com [203.0.113.2]) - by email2.example.com (Postfix) with ESMTP id eeeeeeeeeee - for ; Thu, 29 Apr 2017 23:34:45 +0900 (JST) -Received: from [192.0.2.2] (unknown [203.0.113.22]) - (Authenticated sender: sironeko@example.co.jp) - by relay1.example.com (nekomail) with ESMTP id fffffffff22 - for ; Thu, 29 Apr 2017 23:34:45 +0900 (JST) -Content-Type: multipart/mixed; boundary="===============222222220000222==" -MIME-Version: 1.0 -Subject: Nyaan -From: "Sironeko" -To: kijitora@example.org -Date: Thu, 29 Apr 2017 23:34:45 +0900 -Message-ID: <222222222000.2222.2222222222220000000@NEKO-Nyaan> - ---===============222222220000222== -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 - -Nyaan - ---===============222222220000222==-- - ---2222FFFFEEEE.0000000000/email2.example.com-- diff --git a/set-of-emails/maildir/bsd/rhost-exchangeonline-03.eml b/set-of-emails/maildir/bsd/rhost-exchangeonline-03.eml deleted file mode 100644 index c80457fe..00000000 --- a/set-of-emails/maildir/bsd/rhost-exchangeonline-03.eml +++ /dev/null @@ -1,205 +0,0 @@ -X-Apparently-To: sironeko@example.net; Thu, 29 Apr 2017 23:34:45 +0000 -Return-Path: <> -Received-SPF: none (domain of a2-222.smtp-out.amazonses.com does not designate permitted sender hosts) -X-Originating-IP: [192.0.2.222] -Received: from 127.0.0.1 (EHLO a2-222.smtp-out.amazonses.com) (192.0.2.222) - by relay2.mail.neko2.example.org with SMTP; Thu, 29 Apr 2017 23:34:45 +0000 -MIME-Version: 1.0 -From: MAILER-DAEMON@amazonses.com -To: sironeko@example.net -Date: Thu, 29 Apr 2017 23:34:45 +0000 -Content-Type: multipart/report; report-type=delivery-status; - boundary="22ffee22-2222-2222-2222-2222aaaabbbb" -X-MS-Exchange-Message-Is-Ndr: -Content-Language: en-US -Message-ID: <0000002222002222-22222222-2222-2222-ffff@email.amazonses.com> -Subject: Undeliverable: Nyaan -Auto-Submitted: auto-replied -X-MS-PublicTrafficType: Email -X-MS-TrafficTypeDiagnostic: 0000000000002: -X-MS-Office365-Filtering-Correlation-Id: 00002222-2222-2200-222200220000 -Received-SPF: None (protection.outlook.com: does not designate permitted - sender hosts) -Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=<>; -X-SES-Outgoing: 2017.04.29-192.0.2.222 -Content-Length: 1022 - ---22ffee22-2222-2222-2222-2222aaaabbbb -Content-Type: multipart/alternative; differences=Content-Type; - boundary="00002200-0022-2222-2002-222200220000" - ---00002200-0022-2222-2002-222200220000 -Content-Type: text/plain; charset="us-ascii" -Content-Transfer-Encoding: quoted-printable - -[http://products.office.com/en-us/CMSImages/Office365Logo_Orange.png?versio= -n=3Db8d100a9-0a8b-8e6a-88e1-ef488fee0470] -Your message to kijitora@example.com couldn't be delivered. - -kijitora wasn't found at example.com. - -2222222222220000-222. . . Office 365 kijitora -Action Required Recipient - - -Unknown To address - - -How to Fix It -The address may be misspelled or may not exist. Try one or more of the foll= -owing: - - * Send the message again following these steps: In Outlook, open this n= -on-delivery report (NDR) and choose Send Again from the Report ribbon. In O= -utlook on the web, select this NDR, then select the link "To send this mess= -age again, click here." Then delete and retype the entire recipient address= - If prompted with an Auto-Complete List suggestion don't select it. After = -typing the complete address, click Send. - * Contact the recipient (by phone, for example) to check that the addre= -ss exists and is correct. - * The recipient may have set up email forwarding to an incorrect addres= -s. Ask them to check that any forwarding they've set up is working correctl= -y. - * Clear the recipient Auto-Complete List in Outlook or Outlook on the w= -eb by following the steps in this article: Fix email delivery issues for er= -ror code 5.1.10 in Office 365, and then send the message again. Retype the entire recipient address b= -efore selecting Send. - - -If the problem continues, forward this message to your email admin. If you'= -re an email admin, refer to the More Info for Email Admins section below. - - -Was this helpful? Send feedback to Microsoft. -________________________________ - - -More Info for Email Admins -Status code: 550 5.1.10 - -This error occurs because the sender sent a message to an email address hos= -ted by Office 365 but the address is incorrect or doesn't exist at the dest= -ination domain. The error is reported by the recipient domain's email serve= -r, but most often it must be fixed by the person who sent the message. If t= -he steps in the How to Fix It section above don't fix the problem, and you'= -re the email admin for the recipient, try one or more of the following: - -The email address exists and is correct - Confirm that the recipient addres= -s exists, is correct, and is accepting messages. - -Synchronize your directories - If you have a hybrid environment and are usi= -ng directory synchronization make sure the recipient's email address is syn= -ced correctly in both Office 365 and in your on-premises directory. - -Errant forwarding rule - Check for forwarding rules that aren't behaving as= - expected. Forwarding can be set up by an admin via mail flow rules or mail= -box forwarding address settings, or by the recipient via the Inbox Rules fe= -ature. - -Recipient has a valid license - Make sure the recipient has an Office 365 l= -icense assigned to them. The recipient's email admin can use the Office 365= - admin center to assign a license (Users > Active Users > select the recipi= -ent > Assigned License > Edit). - -Mail flow settings and MX records are not correct - Misconfigured mail flow= - or MX record settings can cause this error. Check your Office 365 mail flo= -w settings to make sure your domain and any mail flow connectors are set up= - correctly. Also, work with your domain registrar to make sure the MX recor= -ds for your domain are configured correctly. - -For more information and additional tips to fix this issue, see Fix email d= -elivery issues for error code 5.1.10 in Office 365. - - -Original Message Details -Created Date: 4/29/2017 11:34:45 PM -Sender Address: 0000002222002222-22222222-2222-2222-ffff@amazonses.com -Recipient Address: kijitora@example.com -Subject: Nyaan - - -Error Details -Reported error: 550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipient not fo= -und by SMTP address lookup -DSN generated by: NEKO220022.namprd22.prod.outlook.com - - -Message Hops -HOP TIME (UTC) FROM TO WITH RELAY TIME -1 4/29/2017 -11:34:45 PM a2-22.smtp-out.amazonses.com NEKO22NYAAN0.mail.protecti= -on.outlook.com Microsoft SMTP Server (version=3DTLS1_0, cipher=3DTLS_= -ECDHE_RSA_WITH_AES_256_CBC_SHA_P384) 2 sec -2 4/29/2017 -11:34:45 PM NEKO22NYAAN0.eop-NAM03.prod.protection.outlook.com CAT= -NEKO0022.outlook.office365.com Microsoft SMTP Server (version=3DTLS1_= -2, cipher=3DTLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P256) 2 sec - -Original Message Headers - -Received: from NEKO22NYAAN22.namprd22.prod.outlook.com (198.51.100.2) by - NEKO220022.namprd22.prod.outlook.com (203.0.113.2) with Microsoft SMT= -P - Server (version=3DTLS1_2, cipher=3DTLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P= -256) id - 15.20.13.10; Thu, 29 Apr 2017 23:34:45 +0000 -Received: from NEKO22NYAAN0.eop-NAM03.prod.protection.outlook.com - (2222:2222:2222:2222::2222) by NEKO22NYAAN22.outlook.office365.com - (2022:2222:2222:2222::2222) with Microsoft SMTP Server (version=3DTLS1_2, - cipher=3DTLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P256) id 15.20.13.10 via - Frontend Transport; Thu, 29 Apr 2017 23:34:45 +0000 -Authentication-Results: spf=3Dpass (sender IP is 192.0.2.2) - smtp.mailfrom=3Damazonses.com; example.com; dkim=3Dpass (signature was veri= -fied) - header.d=3Damazonses.com;example.com; dmarc=3Dnone action=3Dnone - header.from=3Dexample.net; -Received-SPF: Pass (protection.outlook.com: domain of amazonses.com designa= -tes - 192.0.2.2 as permitted sender) receiver=3Dprotection.outlook.com; - client-ip=3D192.0.2.2; helo=3Da2-222.smtp-out.amazonses.com; -Received: from a2-222.smtp-out.amazonses.com (192.0.2.222) by - NEKO22NYAAN0.mail.protection.outlook.com (203.0.113.2) with Microsoft S= -MTP - Server (version=3DTLS1_0, cipher=3DTLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P384= -) id - 15.1.1222.22 via Frontend Transport; Thu, 29 Apr 2017 23:34:45 +0000 -Content-Type: text/html; charset=3Dutf-8 -MIME-Version: 1.0 -Date: Thu, 29 Apr 2017 23:34:45 +0000 -Content-Transfer-Encoding: quoted-printable -Subject: Nyaan -From: "Sironeko, Nyaan" -Message-ID: <2222222222220000-00000000-2222-2222-2222-000000002222-000000@e= -mail.amazonses.com> -To: kijitora@example.com -X-SES-Outgoing: 2017.04.29-192.0.2.2 -Return-Path: 2222222222221111-22222222-2222-0000-eeeeeeeeeeee@amazonses.com -X-MS-PublicTrafficType: Email -X-DkimResult-Test: Passed - ---00002200-0022-2222-2002-222200220000 -Content-Type: text/plain; charset="us-ascii" -Content-Transfer-Encoding: quoted-printable - -Nyaan - ---00002200-0022-2222-2002-222200220000-- - ---22ffee22-2222-2222-2222-2222aaaabbbb -Content-Type: message/delivery-status - -Reporting-MTA: dns;NEKO220022.namprd22.prod.outlook.com -Received-From-MTA: dns;a2-22.smtp-out.amazonses.com -Arrival-Date: Thu, 29 Apr 2017 23:34:45 +0000 - -Final-Recipient: rfc822;kijitora@example.com -Action: failed -Status: 5.1.10 -Diagnostic-Code: smtp;550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipient not found by SMTP address lookup - - ---22ffee22-2222-2222-2222-2222aaaabbbb-- - diff --git a/set-of-emails/maildir/bsd/rhost-googleapps-01.eml b/set-of-emails/maildir/bsd/rhost-googleapps-01.eml deleted file mode 100644 index 473f4190..00000000 --- a/set-of-emails/maildir/bsd/rhost-googleapps-01.eml +++ /dev/null @@ -1,66 +0,0 @@ -Received: from mail4.example.co.jp (1234c.example.com [192.0.2.1]) - by mx.example.jp (8.14.4/8.14.4) with ESMTP id r4B0078w00000 - for ; Mon, 11 May 2013 00:00:00 +0900 (JST) -Date: Mon, 11 May 2013 00:00:00 +0900 -From: Mail Delivery Subsystem -Message-Id: <201305110000000000000.r4B00000000000@mail4.example.co.jp> -To: postmaster@mail4.example.co.jp -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="r4B00000000000.0000000/mail4.example.co.jp" -Subject: Postmaster notify: see transcript for details - -This is a MIME-encapsulated message - ---r4B00000000000.0000000/mail4.example.co.jp - -The original message was received at Mon, 11 May 2013 00:00:00 +0900 -from localhost [127.0.0.1] -with id r4B0037M000000 - - ----- The following addresses had permanent fatal errors ----- - - (reason: 550 5.2.1 The email account that you tried to reach is disabled. g0000000000ggg.00) - - ----- Transcript of session follows ----- -... while talking to aspmx.l.google.com.: ->>> RCPT To: -<<< 550 5.2.1 The email account that you tried to reach is disabled. g0000000000ggg.00 -550 5.1.1 ... User unknown ->>> DATA -<<< 503 5.5.1 RCPT first. g0000000000ggg.00 - ---r4B00000000000.0000000/mail4.example.co.jp -Content-Type: message/delivery-status - -Reporting-MTA: dns; mail4.example.co.jp -Received-From-MTA: DNS; localhost.example.com -Arrival-Date: Mon, 11 May 2013 00:00:00 +0900 - -Final-Recipient: RFC822; shironeko@example.ne.jp -Action: failed -Status: 5.2.1 -Remote-MTA: DNS; aspmx.l.google.com -Diagnostic-Code: SMTP; 550 5.2.1 The email account that you tried to reach is disabled. g0000000000ggg.00 -Last-Attempt-Date: Mon, 11 May 2013 00:00:00 +0900 - ---r4B00000000000.0000007/mail4.example.co.jp -Content-Type: text/rfc822-headers - -Return-Path: -Received: (from webmaster@localhost) - by mail4.example.co.jp (8.14.4/8.14.4/Submit) id r4B003v000000 - for shironeko@example.ne.jp; Mon, 11 May 2013 00:00:00 +0900 -Date: Mon, 11 May 2013 00:00:00 +0900 -Message-Id: <201305110000000000000.r4B003v000000@mail4.example.co.jp> -X-SENDTO: shironeko@example.ne.jp -Reply-To: mikeneko@example.co.jp -To: shironeko@example.ne.jp -From: kijitora@example.org -Subject: Bounce from GoogleApps MTA -MIME-Version: 1.0 -Content-Type: text/plain; charset="ISO-2022-JP" -Content-Transfer-Encoding: 7bit - ---r4B00000000000.0000007/mail4.example.co.jp-- - diff --git a/set-of-emails/maildir/bsd/rhost-googleapps-02.eml b/set-of-emails/maildir/bsd/rhost-googleapps-02.eml deleted file mode 100644 index 4f36ebf1..00000000 --- a/set-of-emails/maildir/bsd/rhost-googleapps-02.eml +++ /dev/null @@ -1,88 +0,0 @@ -Return-Path: <> -X-Original-To: kijitora@example.com -Delivered-To: kijitora@example.com -Received: by mail.example.co.jp (Postfix) - id 49E71FA97E1D; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) -From: MAILER-DAEMON@mail.example.co.jp (Mail Delivery System) -Subject: Undelivered Mail Returned to Sender -To: kijitora@example.com -Auto-Submitted: auto-replied -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="AA92C1B23442.1720293561/mail.example.co.jp" -Content-Transfer-Encoding: 7bit -Message-Id: <20180429233445.A7470DDE1C65@mail.example.co.jp> - -This is a MIME-encapsulated message. - ---AA92C1B23442.1528513261/mail.example.co.jp -Content-Description: Notification -Content-Type: text/plain; charset=us-ascii - -This is the mail system at host mail.example.co.jp. - -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 - -If you do so, please include this problem report. You can -delete your own text from the attached returned message. - - The mail system - -: host aspmx.l.google.com[108.177.97.26] - said: 550-5.1.1 The email account that you tried to reach does not exist. - Please try 550-5.1.1 double-checking the recipient's email address for - typos or 550-5.1.1 unnecessary spaces. Learn more at 550 5.1.1 - https://support.google.com/mail/?p=NoSuchUser e22-n7GpZmsf093195.222 - - gsmtp (in reply to RCPT TO command) - ---AA92C1B23442.1528513261/mail.example.co.jp -Content-Description: Delivery report -Content-Type: message/delivery-status - -Reporting-MTA: dns; mail.example.co.jp -X-Postfix-Queue-ID: AA92C1B23442 -X-Postfix-Sender: rfc822; kijitora@example.com -Arrival-Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) - -Final-Recipient: rfc822; neko-nyaan@example.org -Original-Recipient: rfc822;neko-nyaan@example.org -Action: failed -Status: 5.1.1 -Remote-MTA: dns; aspmx.l.google.com -Diagnostic-Code: smtp; 550-5.1.1 The email account that you tried to reach does - not exist. Please try 550-5.1.1 double-checking the recipient's email - address for typos or 550-5.1.1 unnecessary spaces. Learn more at 550 5.1.1 - https://support.google.com/mail/?p=NoSuchUser e22-n7GpZmsf093195.222 - - gsmtp - ---AA92C1B23442.1528513261/mail.example.co.jp -Content-Description: Undelivered Message -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit - -Received: from localhost (localhost.localdomain [127.0.0.1]) - by mail.example.co.jp (Postfix) with ESMTP id AA92C1B23442 - for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Received: from mail.example.co.jp ([127.0.0.1]) - by localhost (neko1.example.co.jp [127.0.0.1]) (amavisd-new, port 24) - with ESMTP id F3BzCS-VimHsu-rf for ; - Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Received: from localhost (localhost.localdomain [127.0.0.1]) - by mail.example.co.jp (Postfix) with ESMTP id 567963C3A261 - for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -MIME-Version: 1.0 -Content-Type: text/plain; charset=ISO-2022-JP -Content-Transfer-Encoding: quoted-printable -From: "Neko" -To: neko-nyaan@example.org -Subject: Nyaan -Message-Id: <2018042233445.A95F8E533589@mail.example.co.jp> -Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) - -Nyaan - ---AA92C1B23442.1528513261/mail.example.co.jp-- diff --git a/set-of-emails/maildir/bsd/rhost-tencentqq-01.eml b/set-of-emails/maildir/bsd/rhost-tencentqq-01.eml deleted file mode 100644 index bd4befd8..00000000 --- a/set-of-emails/maildir/bsd/rhost-tencentqq-01.eml +++ /dev/null @@ -1,84 +0,0 @@ -Return-Path: <> -X-Original-To: kijitora-nyaan@em.example.com -Delivered-To: kijitora-nyaan@em.example.com -Received: by mail.example.net (Postfix) - id FA704A7BEB41; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) -From: MAILER-DAEMON@mail.example.net (Mail Delivery System) -Subject: Undelivered Mail Returned to Sender -To: kijitora-nyaan@em.example.com -Auto-Submitted: auto-replied -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="886EC3C350D4.1965362333/mail.example.net" -Content-Transfer-Encoding: 7bit -Message-Id: <20180429233445.0D26F68F0B54@mail.example.net> - -This is a MIME-encapsulated message. - ---886EC3C350D4.1965362333/mail.example.net -Content-Description: Notification -Content-Type: text/plain; charset=us-ascii - -This is the mail system at host mail.example.net. - -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 - -If you do so, please include this problem report. You can -delete your own text from the attached returned message. - - The mail system - -: host mx3.qq.com[103.7.30.40] said: 550 Ip frequency - limited [V2VkIEFwciAgMyAxNjozNjo1OSBKU1QgMjAxOQo=/RkNGMjlFQTVEQ0E Blocked - IP 192.0.2.22]. - http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000725 (in - reply to end of DATA command) - ---886EC3C350D4.1965362333/mail.example.net -Content-Description: Delivery report -Content-Type: message/delivery-status - -Reporting-MTA: dns; mail.example.net -X-Postfix-Queue-ID: 92DCED2544DB -X-Postfix-Sender: rfc822; kijitora-nyaan@em.example.com -Arrival-Date: Thu, 6 Nov 2018 23:34:45 +0900 (JST) - -Final-Recipient: rfc822; nekochan@qq.example.cn -Original-Recipient: rfc822;nekochan@qq.example.cn -Action: failed -Status: 5.0.0 -Remote-MTA: dns; mx3.qq.com -Diagnostic-Code: smtp; 550 Ip frequency limited - [V2VkIEFwciAgMyAxNjozNjo1OSBKU1QgMjAxOQo=/RkNGMjlFQTVEQ0E Blocked IP - 192.0.2.22]. - http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000725 - ---886EC3C350D4.1965362333/mail.example.net -Content-Description: Undelivered Message -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit - -Received: from localhost (localhost.localdomain [127.0.0.1]) - by mail.example.net (Postfix) with ESMTP id 8724A00AFDA3 - for ; Thu, 6 Nov 2018 23:34:45 +0900 (JST) -Received: from mail.example.net ([127.0.0.1]) - by localhost (neko1.example.net [127.0.0.1]) (amavisd-new, port 24) - with ESMTP id KCqxsygh6hzy6Mfw for ; - Thu, 6 Nov 2018 23:34:45 +0900 (JST) -Received: from localhost (localhost.localdomain [127.0.0.1]) - by mail.example.net (Postfix) with ESMTP id F6E14AF76696 - for ; Thu, 6 Nov 2018 23:34:45 +0900 (JST) -From: -To: -Subject: Nyaan -Content-type: text/plain; charset=ISO-2022-JP -Message-Id: <20180429233445.1BFAC0EE7C9F@mail.example.net> -Date: Thu, 6 Nov 2018 23:34:45 +0900 (JST) - -Nyaan - ---886EC3C350D4.1965362333/mail.example.net-- diff --git a/set-of-emails/maildir/bsd/rhost-tencentqq-02.eml b/set-of-emails/maildir/bsd/rhost-tencentqq-02.eml deleted file mode 100644 index cc062ffe..00000000 --- a/set-of-emails/maildir/bsd/rhost-tencentqq-02.eml +++ /dev/null @@ -1,84 +0,0 @@ -Return-Path: <> -X-Original-To: kijitora-neko@example.com -Delivered-To: kijitora-neko@example.com -Received: by mail.example.co.jp (Postfix) - id 30EDC7807231; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) -From: MAILER-DAEMON@mail.example.co.jp (Mail Delivery System) -Subject: Undelivered Mail Returned to Sender -To: kijitora-neko@example.com -Auto-Submitted: auto-replied -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="9DD98D518C62.1219773614/mail.example.co.jp" -Content-Transfer-Encoding: 7bit -Message-Id: <20180429233445.FD0D225823D5@mail.example.co.jp> - -This is a MIME-encapsulated message. - ---9DD98D518C62.1219773614/mail.example.co.jp -Content-Description: Notification -Content-Type: text/plain; charset=us-ascii - -This is the mail system at host mail.example.co.jp. - -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 - -If you do so, please include this problem report. You can -delete your own text from the attached returned message. - - The mail system - -: host mx3.qq.com[103.7.30.40] said: 550 Connection - frequency limited [Q1YzZ3kxdnRYYnpkbVZ0MAo+QTMzMTMzRjUxNzQ4Cg/MTI3MTY2MDge= - Blocked IP 192.0.2.22]. - http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000722 (in - reply to end of DATA command) - ---9DD98D518C62.1219773614/mail.example.co.jp -Content-Description: Delivery report -Content-Type: message/delivery-status - -Reporting-MTA: dns; mail.example.co.jp -X-Postfix-Queue-ID: 9DD98D518C62 -X-Postfix-Sender: rfc822; kijitora-neko@example.com -Arrival-Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) - -Final-Recipient: rfc822; neko@qq.example.com -Original-Recipient: rfc822;neko@qq.example.com -Action: failed -Status: 5.0.0 -Remote-MTA: dns; mx3.qq.com -Diagnostic-Code: smtp; 550 Connection frequency limited - [Q1YzZ3kxdnRYYnpkbVZ0MAo+QTMzMTMzRjUxNzQ4Cg/MTI3MTY2MDge= Blocked IP - 192.0.2.22]. - http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000722 - ---9DD98D518C62.1219773614/mail.example.co.jp -Content-Description: Undelivered Message -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit - -Received: from localhost (localhost.localdomain [127.0.0.1]) - by mail.example.co.jp (Postfix) with ESMTP id 90B28F34CE21 - for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Received: from mail.example.co.jp ([127.0.0.1]) - by localhost (neko1.example.co.jp [127.0.0.1]) (amavisd-new, port 24) - with ESMTP id 9WGfDFvM9hzgWRVW for ; - Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Received: from localhost (localhost.localdomain [127.0.0.1]) - by mail.example.co.jp (Postfix) with ESMTP id AB2A08507515 - for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -From: "Kijitora" -To: "Nyaan" -Subject: Nyaan -Content-type: text/plain; charset=ISO-2022-JP -Message-Id: <20180429233445.4278803910AC@mail.example.co.jp> -Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) - -Nyaan - ---9DD98D518C62.1219773614/mail.example.co.jp-- diff --git a/set-of-emails/maildir/bsd/rhost-tencentqq-03.eml b/set-of-emails/maildir/bsd/rhost-tencentqq-03.eml deleted file mode 100644 index 9ba176ea..00000000 --- a/set-of-emails/maildir/bsd/rhost-tencentqq-03.eml +++ /dev/null @@ -1,81 +0,0 @@ -Return-Path: <> -X-Original-To: kijitora@example.net -Delivered-To: kijitora@example.net -Received: by mail.example.com (Postfix) - id 77DD768F06BA; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) -From: MAILER-DAEMON@mail.example.com (Mail Delivery System) -Subject: Undelivered Mail Returned to Sender -To: kijitora@example.net -Auto-Submitted: auto-replied -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="815BDAE8A2BF.1707680793/mail.example.com" -Content-Transfer-Encoding: 7bit -Message-Id: <20180429233445.77DD768F06BA@mail.example.com> - -This is a MIME-encapsulated message. - ---815BDAE8A2BF.1707680793/mail.example.com -Content-Description: Notification -Content-Type: text/plain; charset=us-ascii - -This is the mail system at host mail.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 - -If you do so, please include this problem report. You can -delete your own text from the attached returned message. - - The mail system - -: host mx3.qq.com[103.7.30.40] said: 550 DMARC check - failed. - http://service.mail.qq.com/cgi-bin/help?subtype=1&&no=1001508&&id=16. (in - reply to end of DATA command) - ---815BDAE8A2BF.1707680793/mail.example.com -Content-Description: Delivery report -Content-Type: message/delivery-status - -Reporting-MTA: dns; mail.example.com -X-Postfix-Queue-ID: FC67BF0C6926 -X-Postfix-Sender: rfc822; kijitora@example.net -Arrival-Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) - -Final-Recipient: rfc822; neko@qq.example.cn -Original-Recipient: rfc822;neko@qq.example.cn -Action: failed -Status: 5.0.0 -Remote-MTA: dns; mx3.qq.com -Diagnostic-Code: smtp; 550 DMARC check failed. - http://service.mail.qq.com/cgi-bin/help?subtype=1&&no=1001508&&id=16. - ---815BDAE8A2BF.1707680793/mail.example.com -Content-Description: Undelivered Message -Content-Type: message/rfc822 -Content-Transfer-Encoding: 7bit - -Received: from localhost (localhost.localdomain [127.0.0.1]) - by mail.example.com (Postfix) with ESMTP id 815BDAE8A2BF - for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Received: from mail.example.com ([127.0.0.1]) - by localhost (neko1.example.com [127.0.0.1]) (amavisd-new, port 24) - with ESMTP id FpR1e6-NIX4NY-bn for ; - Thu, 29 Apr 2018 23:34:45 +0900 (JST) -Received: from localhost (localhost.localdomain [127.0.0.1]) - by mail.example.com (Postfix) with ESMTP id 663370D36901 - for ; Thu, 29 Apr 2018 23:34:45 +0900 (JST) -From: "Kijitora" -To: neko@qq.example.cn -Subject: Nyaan -Content-type: text/plain; charset=ISO-2022-JP -Message-Id: <20180429233445.939E1DDF19E1@mail.example.com> -Date: Thu, 29 Apr 2018 23:34:45 +0900 (JST) - -Nyaan - ---815BDAE8A2BF.1707680793/mail.example.com--