sisimai 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/rake-test.yml +1 -1
- data/ChangeLog.md +102 -0
- data/Makefile +4 -2
- data/README-JA.md +23 -16
- data/README.md +22 -15
- data/lib/sisimai/arf.rb +121 -210
- data/lib/sisimai/fact.rb +208 -158
- data/lib/sisimai/lda.rb +98 -0
- data/lib/sisimai/lhost/activehunter.rb +1 -1
- data/lib/sisimai/lhost/amazonses.rb +185 -301
- data/lib/sisimai/lhost/apachejames.rb +48 -51
- data/lib/sisimai/lhost/biglobe.rb +1 -2
- data/lib/sisimai/lhost/courier.rb +10 -8
- data/lib/sisimai/lhost/domino.rb +25 -25
- data/lib/sisimai/lhost/dragonfly.rb +3 -4
- data/lib/sisimai/lhost/einsundeins.rb +3 -4
- data/lib/sisimai/lhost/exchange2003.rb +6 -8
- data/lib/sisimai/lhost/exchange2007.rb +111 -101
- data/lib/sisimai/lhost/exim.rb +232 -242
- data/lib/sisimai/lhost/ezweb.rb +43 -51
- data/lib/sisimai/lhost/fml.rb +2 -3
- data/lib/sisimai/lhost/gmail.rb +32 -28
- data/lib/sisimai/lhost/gmx.rb +4 -16
- data/lib/sisimai/lhost/googlegroups.rb +9 -8
- data/lib/sisimai/lhost/googleworkspace.rb +94 -0
- data/lib/sisimai/lhost/imailserver.rb +7 -16
- data/lib/sisimai/lhost/interscanmss.rb +1 -1
- data/lib/sisimai/lhost/kddi.rb +3 -4
- data/lib/sisimai/lhost/mailfoundry.rb +2 -5
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +1 -2
- data/lib/sisimai/lhost/messagingserver.rb +14 -13
- data/lib/sisimai/lhost/mfilter.rb +4 -3
- data/lib/sisimai/lhost/notes.rb +2 -4
- data/lib/sisimai/lhost/opensmtpd.rb +2 -2
- data/lib/sisimai/lhost/postfix.rb +25 -27
- data/lib/sisimai/lhost/qmail.rb +130 -106
- data/lib/sisimai/lhost/sendmail.rb +19 -18
- data/lib/sisimai/lhost/v5sendmail.rb +88 -60
- data/lib/sisimai/lhost/verizon.rb +2 -2
- data/lib/sisimai/lhost/x1.rb +1 -1
- data/lib/sisimai/lhost/x2.rb +1 -2
- data/lib/sisimai/lhost/x3.rb +2 -2
- data/lib/sisimai/lhost/x6.rb +1 -1
- data/lib/sisimai/lhost/zoho.rb +2 -2
- data/lib/sisimai/lhost.rb +18 -21
- data/lib/sisimai/message.rb +93 -146
- data/lib/sisimai/order.rb +21 -77
- data/lib/sisimai/reason/authfailure.rb +1 -4
- data/lib/sisimai/reason/badreputation.rb +2 -2
- data/lib/sisimai/reason/blocked.rb +7 -10
- data/lib/sisimai/reason/contenterror.rb +7 -1
- data/lib/sisimai/reason/exceedlimit.rb +1 -4
- data/lib/sisimai/reason/failedstarttls.rb +42 -0
- data/lib/sisimai/reason/filtered.rb +5 -4
- data/lib/sisimai/reason/hasmoved.rb +1 -2
- data/lib/sisimai/reason/hostunknown.rb +3 -3
- data/lib/sisimai/reason/mailboxfull.rb +2 -4
- data/lib/sisimai/reason/mailererror.rb +1 -2
- data/lib/sisimai/reason/mesgtoobig.rb +2 -4
- data/lib/sisimai/reason/norelaying.rb +2 -3
- data/lib/sisimai/reason/notaccept.rb +2 -3
- data/lib/sisimai/reason/notcompliantrfc.rb +10 -4
- data/lib/sisimai/reason/rejected.rb +1 -1
- data/lib/sisimai/reason/requireptr.rb +2 -2
- data/lib/sisimai/reason/securityerror.rb +1 -3
- data/lib/sisimai/reason/spamdetected.rb +6 -8
- data/lib/sisimai/reason/speeding.rb +1 -2
- data/lib/sisimai/reason/suppressed.rb +36 -0
- data/lib/sisimai/reason/suspend.rb +1 -3
- data/lib/sisimai/reason/systemerror.rb +5 -0
- data/lib/sisimai/reason/toomanyconn.rb +1 -2
- data/lib/sisimai/reason/userunknown.rb +1 -1
- data/lib/sisimai/reason/virusdetected.rb +5 -6
- data/lib/sisimai/reason.rb +77 -73
- data/lib/sisimai/rfc1123.rb +152 -0
- data/lib/sisimai/rfc1894.rb +102 -62
- data/lib/sisimai/rfc2045.rb +2 -1
- data/lib/sisimai/rfc3464/thirdparty.rb +102 -0
- data/lib/sisimai/rfc3464.rb +222 -343
- data/lib/sisimai/rfc3834.rb +1 -1
- data/lib/sisimai/rfc5322.rb +7 -17
- data/lib/sisimai/rfc791.rb +69 -0
- data/lib/sisimai/rhost/aol.rb +36 -0
- data/lib/sisimai/rhost/apple.rb +5 -2
- data/lib/sisimai/rhost/cox.rb +3 -2
- data/lib/sisimai/rhost/facebook.rb +100 -0
- data/lib/sisimai/rhost/franceptt.rb +3 -2
- data/lib/sisimai/rhost/godaddy.rb +3 -2
- data/lib/sisimai/rhost/google.rb +19 -17
- data/lib/sisimai/rhost/gsuite.rb +42 -0
- data/lib/sisimai/rhost/iua.rb +3 -3
- data/lib/sisimai/rhost/kddi.rb +3 -2
- data/lib/sisimai/rhost/messagelabs.rb +37 -0
- data/lib/sisimai/rhost/microsoft.rb +56 -49
- data/lib/sisimai/rhost/mimecast.rb +29 -27
- data/lib/sisimai/rhost/nttdocomo.rb +4 -3
- data/lib/sisimai/rhost/outlook.rb +36 -0
- data/lib/sisimai/rhost/spectrum.rb +3 -2
- data/lib/sisimai/rhost/tencent.rb +3 -2
- data/lib/sisimai/rhost/yahooinc.rb +4 -3
- data/lib/sisimai/rhost.rb +69 -39
- data/lib/sisimai/smtp/command.rb +31 -21
- data/lib/sisimai/smtp/failure.rb +103 -0
- data/lib/sisimai/smtp/reply.rb +29 -25
- data/lib/sisimai/smtp/status.rb +36 -19
- data/lib/sisimai/smtp/transcript.rb +15 -15
- data/lib/sisimai/string.rb +0 -46
- data/lib/sisimai/version.rb +1 -1
- data/set-of-emails/maildir/bsd/lhost-postfix-30.eml +81 -81
- data/set-of-emails/maildir/bsd/{lhost-aol-03.eml → rhost-aol-03.eml} +1264 -1264
- data/set-of-emails/maildir/bsd/{lhost-aol-04.eml → rhost-aol-04.eml} +1260 -1260
- data/set-of-emails/maildir/bsd/{lhost-aol-05.eml → rhost-aol-05.eml} +105 -105
- data/set-of-emails/maildir/bsd/{lhost-aol-06.eml → rhost-aol-06.eml} +105 -105
- data/set-of-emails/maildir/bsd/rhost-gsuite-01.eml +189 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-02.eml +180 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-03.eml +251 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-04.eml +211 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-05.eml +226 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-06.eml +257 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-07.eml +289 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-08.eml +231 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-09.eml +231 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-10.eml +254 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-11.eml +228 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-12.eml +271 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-13.eml +261 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-14.eml +273 -0
- data/set-of-emails/maildir/bsd/rhost-gsuite-15.eml +229 -0
- data/set-of-emails/maildir/bsd/{lhost-messagelabs-01.eml → rhost-messagelabs-01.eml} +93 -93
- data/set-of-emails/maildir/bsd/rhost-outlook-01.eml +72 -0
- data/set-of-emails/maildir/bsd/rhost-outlook-02.eml +72 -0
- data/set-of-emails/maildir/bsd/rhost-outlook-03.eml +72 -0
- data/set-of-emails/maildir/bsd/rhost-outlook-04.eml +79 -0
- data/set-of-emails/maildir/bsd/rhost-outlook-06.eml +75 -0
- data/set-of-emails/maildir/bsd/rhost-outlook-07.eml +70 -0
- data/set-of-emails/maildir/bsd/rhost-outlook-08.eml +70 -0
- data/set-of-emails/maildir/bsd/rhost-outlook-09.eml +56 -0
- data/set-of-emails/maildir/tmp/arf-22.eml +49 -0
- data/set-of-emails/maildir/tmp/arf-23.eml +49 -0
- data/set-of-emails/maildir/tmp/arf-24.eml +50 -0
- data/set-of-emails/maildir/tmp/lhost-exim-07.eml +28 -0
- metadata +73 -56
- data/lib/sisimai/lhost/amavis.rb +0 -163
- data/lib/sisimai/lhost/amazonworkmail.rb +0 -127
- data/lib/sisimai/lhost/aol.rb +0 -125
- data/lib/sisimai/lhost/barracuda.rb +0 -92
- data/lib/sisimai/lhost/bigfoot.rb +0 -125
- data/lib/sisimai/lhost/facebook.rb +0 -188
- data/lib/sisimai/lhost/gsuite.rb +0 -194
- data/lib/sisimai/lhost/mailru.rb +0 -214
- data/lib/sisimai/lhost/mcafee.rb +0 -109
- data/lib/sisimai/lhost/messagelabs.rb +0 -120
- data/lib/sisimai/lhost/mxlogic.rb +0 -198
- data/lib/sisimai/lhost/office365.rb +0 -252
- data/lib/sisimai/lhost/outlook.rb +0 -129
- data/lib/sisimai/lhost/powermta.rb +0 -118
- data/lib/sisimai/lhost/receivingses.rb +0 -126
- data/lib/sisimai/lhost/sendgrid.rb +0 -150
- data/lib/sisimai/lhost/surfcontrol.rb +0 -105
- data/lib/sisimai/lhost/x4.rb +0 -269
- data/lib/sisimai/lhost/x5.rb +0 -112
- data/lib/sisimai/lhost/yahoo.rb +0 -102
- data/lib/sisimai/lhost/yandex.rb +0 -118
- data/lib/sisimai/mda.rb +0 -121
- data/lib/sisimai/smtp/error.rb +0 -119
- /data/set-of-emails/maildir/bsd/{lhost-googlegroups-15.eml → lhost-googleworkspace-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-x4-08.eml → lhost-x2-06.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-01.eml → rfc3464-51.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-03.eml → rfc3464-52.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-04.eml → rfc3464-53.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-05.eml → rfc3464-54.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-06.eml → rfc3464-55.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-07.eml → rfc3464-56.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-08.eml → rfc3464-57.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-09.eml → rfc3464-58.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-10.eml → rfc3464-59.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-11.eml → rfc3464-60.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-12.eml → rfc3464-61.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-13.eml → rfc3464-62.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-14.eml → rfc3464-63.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-15.eml → rfc3464-64.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-gsuite-02.eml → rfc3464-65.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-aol-01.eml → rhost-aol-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-aol-02.eml → rhost-aol-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-facebook-03.eml → rhost-facebook-03.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-facebook-04.eml → rhost-facebook-04.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-messagelabs-02.eml → rhost-messagelabs-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{lhost-messagelabs-03.eml → rhost-messagelabs-03.eml} +0 -0
- /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-37.eml +0 -0
- /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-38.eml +0 -0
- /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-39.eml +0 -0
data/lib/sisimai/reason.rb
CHANGED
@@ -1,19 +1,29 @@
|
|
1
1
|
module Sisimai
|
2
2
|
# Sisimai::Reason detects the bounce reason from the Hash table which is to be constructed to
|
3
|
-
# Sisimai::Fact object as an argument of
|
3
|
+
# Sisimai::Fact object as an argument of find() method. This class is called only Sisimai::Fact.
|
4
4
|
module Reason
|
5
5
|
class << self
|
6
6
|
# All the error reason list Sisimai support
|
7
7
|
# @return [Array] Reason list
|
8
8
|
def index
|
9
9
|
return %w[
|
10
|
-
AuthFailure BadReputation Blocked ContentError ExceedLimit Expired Filtered
|
11
|
-
HostUnknown MailboxFull MailerError MesgTooBig NetworkError NotAccept NotCompliantRFC
|
12
|
-
OnHold Rejected NoRelaying Speeding SpamDetected VirusDetected PolicyViolation
|
13
|
-
|
10
|
+
AuthFailure BadReputation Blocked ContentError ExceedLimit Expired FailedSTARTTLS Filtered
|
11
|
+
HasMoved HostUnknown MailboxFull MailerError MesgTooBig NetworkError NotAccept NotCompliantRFC
|
12
|
+
OnHold Rejected NoRelaying Speeding SpamDetected VirusDetected PolicyViolation SecurityError
|
13
|
+
Suspend RequirePTR SystemError SystemFull TooManyConn Suppressed UserUnknown SyntaxError
|
14
14
|
]
|
15
15
|
end
|
16
16
|
|
17
|
+
# @abstract is_explicit() returns 0 when the argument is empty or is "undefined" or is "onhold"
|
18
|
+
# @param string argv1 Reason name
|
19
|
+
# @return bool false: The reaosn is not explicit
|
20
|
+
def is_explicit(argv1 = '')
|
21
|
+
return false if argv1.nil?
|
22
|
+
return false if argv1.empty?
|
23
|
+
return false if argv1 == "undefined" || argv1 == "onhold" || argv1.empty?
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
|
17
27
|
# @abstract Returns Sisimai::Reason::* module path table
|
18
28
|
# @return [Hash] Module path table
|
19
29
|
# @since v4.25.6
|
@@ -29,8 +39,7 @@ module Sisimai
|
|
29
39
|
def retry
|
30
40
|
return {
|
31
41
|
'undefined' => true, 'onhold' => true, 'systemerror' => true, 'securityerror' => true,
|
32
|
-
'expired' => true, '
|
33
|
-
'userunknown' => true
|
42
|
+
'expired' => true, 'networkerror' => true, 'hostunknown' => true, 'userunknown' => true
|
34
43
|
}.freeze
|
35
44
|
end
|
36
45
|
ModulePath = Sisimai::Reason.path
|
@@ -38,19 +47,19 @@ module Sisimai
|
|
38
47
|
ClassOrder = [
|
39
48
|
%w[
|
40
49
|
MailboxFull MesgTooBig ExceedLimit Suspend HasMoved NoRelaying AuthFailure UserUnknown
|
41
|
-
Filtered RequirePTR NotCompliantRFC BadReputation Rejected HostUnknown
|
42
|
-
TooManyConn Blocked
|
50
|
+
Filtered RequirePTR NotCompliantRFC BadReputation ContentError Rejected HostUnknown
|
51
|
+
SpamDetected Speeding TooManyConn Blocked
|
43
52
|
],
|
44
53
|
%w[
|
45
54
|
MailboxFull AuthFailure BadReputation Speeding SpamDetected VirusDetected PolicyViolation
|
46
55
|
NoRelaying SystemError NetworkError Suspend ContentError SystemFull NotAccept Expired
|
47
|
-
SecurityError MailerError
|
56
|
+
FailedSTARTTLS SecurityError Suppressed MailerError
|
48
57
|
],
|
49
58
|
%w[
|
50
59
|
MailboxFull MesgTooBig ExceedLimit Suspend UserUnknown Filtered Rejected HostUnknown
|
51
|
-
SpamDetected Speeding TooManyConn Blocked SpamDetected AuthFailure
|
52
|
-
SystemError NetworkError Suspend Expired ContentError HasMoved SystemFull
|
53
|
-
MailerError NoRelaying SyntaxError OnHold
|
60
|
+
SpamDetected Speeding TooManyConn Blocked SpamDetected AuthFailure FailedSTARTTLS
|
61
|
+
SecurityError SystemError NetworkError Suspend Expired ContentError HasMoved SystemFull
|
62
|
+
NotAccept MailerError NoRelaying Suppressed SyntaxError OnHold
|
54
63
|
]
|
55
64
|
]
|
56
65
|
|
@@ -58,7 +67,7 @@ module Sisimai
|
|
58
67
|
# @param [Hash] argvs Decoded email object
|
59
68
|
# @return [String, nil] Bounce reason or nil if the argument is missing or not Hash
|
60
69
|
# @see anotherone
|
61
|
-
def
|
70
|
+
def find(argvs)
|
62
71
|
return nil unless argvs
|
63
72
|
unless GetRetried[argvs['reason']]
|
64
73
|
# Return reason text already decided except reason match with the regular expression of
|
@@ -111,10 +120,10 @@ module Sisimai
|
|
111
120
|
return reasontext
|
112
121
|
end
|
113
122
|
|
114
|
-
# Detect the other bounce reason, fall back method for
|
123
|
+
# Detect the other bounce reason, fall back method for find()
|
115
124
|
# @param [Hash] argvs Decoded email object
|
116
125
|
# @return [String, Nil] Bounce reason or nli if the argument is missing or not Hash
|
117
|
-
# @see
|
126
|
+
# @see find()
|
118
127
|
def anotherone(argvs)
|
119
128
|
return argvs['reason'] unless argvs['reason'].empty?
|
120
129
|
|
@@ -124,65 +133,60 @@ module Sisimai
|
|
124
133
|
actiontext = argvs['action'] || ''
|
125
134
|
statuscode = argvs['deliverystatus'] || ''
|
126
135
|
reasontext = Sisimai::SMTP::Status.name(statuscode) || ''
|
136
|
+
trytomatch = reasontext.empty? ? true : false
|
137
|
+
trytomatch ||= true if GetRetried[reasontext] || codeformat != 'SMTP'
|
127
138
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
begin
|
141
|
-
require ModulePath[p]
|
142
|
-
r = Module.const_get(p)
|
143
|
-
rescue
|
144
|
-
warn ' ***warning: Failed to load ' << p
|
145
|
-
next
|
146
|
-
end
|
147
|
-
|
148
|
-
next unless r.match(issuedcode)
|
149
|
-
reasontext = e.downcase
|
150
|
-
break
|
151
|
-
end
|
152
|
-
throw :TRY_TO_MATCH unless reasontext.empty?
|
153
|
-
|
154
|
-
# Check the value of Status:
|
155
|
-
code2digit = statuscode[0, 3] || ''
|
156
|
-
if code2digit == '5.6' || code2digit == '4.6'
|
157
|
-
# X.6.0 Other or undefined media error
|
158
|
-
reasontext = 'contenterror'
|
159
|
-
|
160
|
-
elsif code2digit == '5.7' || code2digit == '4.7'
|
161
|
-
# X.7.0 Other or undefined security status
|
162
|
-
reasontext = 'securityerror'
|
163
|
-
|
164
|
-
elsif codeformat.start_with?('X-UNIX')
|
165
|
-
# Diagnostic-Code: X-UNIX; ...
|
166
|
-
reasontext = 'mailererror'
|
167
|
-
|
168
|
-
else
|
169
|
-
# 50X Syntax Error?
|
170
|
-
require 'sisimai/reason/syntaxerror'
|
171
|
-
reasontext = 'syntaxerror' if Sisimai::Reason::SyntaxError.true(argvs)
|
172
|
-
end
|
173
|
-
throw :TRY_TO_MATCH unless reasontext.empty?
|
174
|
-
|
175
|
-
# Check the value of Action: field, first
|
176
|
-
if actiontext.start_with?('delayed', 'expired')
|
177
|
-
# Action: delayed, expired
|
178
|
-
reasontext = 'expired'
|
179
|
-
else
|
180
|
-
# Rejected at connection or after EHLO|HELO
|
181
|
-
thecommand = argvs['smtpcommand'] || ''
|
182
|
-
reasontext = 'blocked' if %w[HELO EHLO].index(thecommand)
|
139
|
+
while trytomatch
|
140
|
+
# Could not decide the reason by the value of Status:
|
141
|
+
ClassOrder[1].each do |e|
|
142
|
+
# Trying to match with other patterns in Sisimai::Reason::* classes
|
143
|
+
p = 'Sisimai::Reason::' << e
|
144
|
+
r = nil
|
145
|
+
begin
|
146
|
+
require ModulePath[p]
|
147
|
+
r = Module.const_get(p)
|
148
|
+
rescue
|
149
|
+
warn ' ***warning: Failed to load ' << p
|
150
|
+
next
|
183
151
|
end
|
184
|
-
|
152
|
+
|
153
|
+
next unless r.match(issuedcode)
|
154
|
+
reasontext = e.downcase
|
155
|
+
break
|
156
|
+
end
|
157
|
+
break unless reasontext.empty?
|
158
|
+
|
159
|
+
# Check the value of Status:
|
160
|
+
code2digit = statuscode[0, 3] || ''
|
161
|
+
if code2digit == '5.6' || code2digit == '4.6'
|
162
|
+
# X.6.0 Other or undefined media error
|
163
|
+
reasontext = 'contenterror'
|
164
|
+
|
165
|
+
elsif code2digit == '5.7' || code2digit == '4.7'
|
166
|
+
# X.7.0 Other or undefined security status
|
167
|
+
reasontext = 'securityerror'
|
168
|
+
|
169
|
+
elsif codeformat.start_with?('X-UNIX')
|
170
|
+
# Diagnostic-Code: X-UNIX; ...
|
171
|
+
reasontext = 'mailererror'
|
172
|
+
|
173
|
+
else
|
174
|
+
# 50X Syntax Error?
|
175
|
+
require 'sisimai/reason/syntaxerror'
|
176
|
+
reasontext = 'syntaxerror' if Sisimai::Reason::SyntaxError.true(argvs)
|
185
177
|
end
|
178
|
+
break unless reasontext.empty?
|
179
|
+
|
180
|
+
# Check the value of Action: field, first
|
181
|
+
if actiontext.start_with?('delayed', 'expired')
|
182
|
+
# Action: delayed, expired
|
183
|
+
reasontext = 'expired'
|
184
|
+
else
|
185
|
+
# Rejected at connection or after EHLO|HELO
|
186
|
+
thecommand = argvs['command'] || ''
|
187
|
+
reasontext = 'blocked' if %w[HELO EHLO].index(thecommand)
|
188
|
+
end
|
189
|
+
break
|
186
190
|
end
|
187
191
|
return reasontext
|
188
192
|
end
|
@@ -222,7 +226,7 @@ module Sisimai
|
|
222
226
|
else
|
223
227
|
# Detect the bounce reason from "Status:" code
|
224
228
|
require 'sisimai/smtp/status'
|
225
|
-
cv = Sisimai::SMTP::Status.find(argv1)
|
229
|
+
cv = Sisimai::SMTP::Status.find(argv1)
|
226
230
|
reasontext = Sisimai::SMTP::Status.name(cv) || 'undefined'
|
227
231
|
end
|
228
232
|
return reasontext
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Sisimai
|
2
|
+
# Sisimai::RFC1123 is a class related to the Internet host
|
3
|
+
module RFC1123
|
4
|
+
class << self
|
5
|
+
require 'sisimai/string'
|
6
|
+
Sandwiched = [
|
7
|
+
# (Postfix) postfix/src/smtp/smtp_proto.c: "host %s said: %s (in reply to %s)",
|
8
|
+
# - <kijitora@example.com>: host re2.example.com[198.51.100.2] said: 550 ...
|
9
|
+
# - <kijitora@example.org>: host r2.example.org[198.51.100.18] refused to talk to me:
|
10
|
+
["host ", " said: "],
|
11
|
+
["host ", " talk to me: "],
|
12
|
+
["while talking to ", ":"], # (Sendmail) ... while talking to mx.bouncehammer.jp.:
|
13
|
+
["host ", " ["], # (Exim) host mx.example.jp [192.0.2.20]: 550 5.7.0
|
14
|
+
[" by ", ". ["], # (Gmail) ...for the recipient domain example.jp by mx.example.jp. [192.0.2.1].
|
15
|
+
|
16
|
+
# (MailFoundry)
|
17
|
+
# - Delivery failed for the following reason: Server mx22.example.org[192.0.2.222] failed with: 550...
|
18
|
+
# - Delivery failed for the following reason: mail.example.org[192.0.2.222] responded with failure: 552..
|
19
|
+
["delivery failed for the following reason: ", " with"],
|
20
|
+
["remote system: ", "("], # (MessagingServer) Remote system: dns;mx.example.net (mx. --
|
21
|
+
["smtp server <", ">"], # (X6) SMTP Server <smtpd.libsisimai.org> rejected recipient ...
|
22
|
+
["-mta: ", ">"], # (MailMarshal) Reporting-MTA: <rr1.example.com>
|
23
|
+
[" : ", "["], # (SendGrid) cat:000000:<cat@example.jp> : 192.0.2.1 : mx.example.jp:[192.0.2.2]...
|
24
|
+
].freeze
|
25
|
+
StartAfter = [
|
26
|
+
"generating server: ", # (Exchange2007) en-US/Generating server: mta4.example.org
|
27
|
+
"serveur de g", # (Exchange2007) fr-FR/Serveur de g辿n辿ration
|
28
|
+
"server di generazione", # (Exchange2007) it-CH
|
29
|
+
"genererande server", # (Exchange2007) sv-SE
|
30
|
+
].freeze
|
31
|
+
ExistUntil = [
|
32
|
+
" did not like our ", # (Dragonfly) mail-inbound.libsisimai.net [192.0.2.25] did not like our DATA: ...
|
33
|
+
].freeze
|
34
|
+
|
35
|
+
# Returns "true" when the given string is a valid internet host
|
36
|
+
# @param [String] argv0 Hostname
|
37
|
+
# @return [Boolean] false: is not a valid internet host, true: is a valid interneet host
|
38
|
+
# @since v5.2.0
|
39
|
+
def is_internethost(argv0 = '')
|
40
|
+
return false unless argv0
|
41
|
+
return false if argv0.size < 4
|
42
|
+
return false if argv0.size > 255
|
43
|
+
return false if argv0.include?(".") == false
|
44
|
+
return false if argv0.include?("..")
|
45
|
+
return false if argv0.include?("--")
|
46
|
+
return false if argv0.start_with?(".")
|
47
|
+
return false if argv0.start_with?("-")
|
48
|
+
return false if argv0.end_with?("-")
|
49
|
+
|
50
|
+
hostnameok = true
|
51
|
+
characters = argv0.upcase.split("")
|
52
|
+
characters.each do |e|
|
53
|
+
# Check each characater is a number or an alphabet
|
54
|
+
f = e.ord
|
55
|
+
if f < 45 then hostnameok = false; break; end # 45 = '-'
|
56
|
+
if f == 47 then hostnameok = false; break; end # 47 = '/'
|
57
|
+
if f > 57 && f < 65 then hostnameok = false; break; end # 57 = '9', 65 = 'A'
|
58
|
+
if f > 90 then hostnameok = false; break; end # 90 = 'Z'
|
59
|
+
end
|
60
|
+
return false if hostnameok == false
|
61
|
+
|
62
|
+
p1 = argv0.rindex(".")
|
63
|
+
cv = argv0[p1 + 1, argv0.size - p1]; return false if cv.size > 63
|
64
|
+
cv.split("").each do |e|
|
65
|
+
# The top level domain should not include a number
|
66
|
+
f = e.ord
|
67
|
+
if f > 47 && f < 58 then hostnameok = false; break; end
|
68
|
+
end
|
69
|
+
return hostnameok
|
70
|
+
end
|
71
|
+
|
72
|
+
# find() returns a valid internet hostname found from the argument
|
73
|
+
# @param string argv1 String including hostnames
|
74
|
+
# @return string A valid internet hostname found in the argument
|
75
|
+
# @since v5.2.0
|
76
|
+
def find(argv1 = "")
|
77
|
+
return "" unless argv1
|
78
|
+
return "" unless argv1.size > 4
|
79
|
+
|
80
|
+
sourcetext = argv1.downcase
|
81
|
+
sourcelist = []
|
82
|
+
foundtoken = []
|
83
|
+
thelongest = 0
|
84
|
+
hostnameis = ""
|
85
|
+
|
86
|
+
# Replace some string for splitting by " "
|
87
|
+
# - mx.example.net[192.0.2.1] => mx.example.net [192.0.2.1]
|
88
|
+
# - mx.example.jp:[192.0.2.1] => mx.example.jp :[192.0.2.1]
|
89
|
+
sourcetext = sourcetext.gsub("[", " [").gsub("(", " (").gsub("<", " <") # Prefix a space character before each bracket
|
90
|
+
sourcetext = sourcetext.gsub("]", "] ").gsub(")", ") ").gsub(">", "> ") # Suffix a space character behind each bracket
|
91
|
+
sourcetext = sourcetext.gsub(":", ": ").gsub(";", "; ") # Suffix a space character behind : and ;
|
92
|
+
sourcetext = Sisimai::String.sweep(sourcetext)
|
93
|
+
|
94
|
+
catch :MAKELIST do
|
95
|
+
Sandwiched.each do |e|
|
96
|
+
# Check a hostname exists between the $e->[0] and $e->[1] at array "Sandwiched"
|
97
|
+
# Each array in Sandwiched have 2 elements
|
98
|
+
next unless Sisimai::String.aligned(sourcetext, e)
|
99
|
+
|
100
|
+
p1 = sourcetext.index(e[0])
|
101
|
+
p2 = sourcetext.index(e[1])
|
102
|
+
cw = e[0].size
|
103
|
+
next if p1 + cw >= p2
|
104
|
+
|
105
|
+
sourcelist = sourcetext[p1 + cw, p2 - cw - p1].split(" ")
|
106
|
+
throw :MAKELIST
|
107
|
+
end
|
108
|
+
|
109
|
+
# Check other patterns which are not sandwiched
|
110
|
+
StartAfter.each do |e|
|
111
|
+
# StartAfter have some strings, not an array
|
112
|
+
p1 = sourcetext.index(e); next if p1.nil?
|
113
|
+
cw = e.size
|
114
|
+
sourcelist = sourcetext[p1 + cw, sourcetext.size].split(" ")
|
115
|
+
throw :MAKELIST
|
116
|
+
end
|
117
|
+
ExistUntil.each do |e|
|
118
|
+
# ExistUntil have some strings, not an array
|
119
|
+
p1 = sourcetext.index(e); next unless p1
|
120
|
+
sourcelist = sourcetext[0, p1].split(" ")
|
121
|
+
throw :MAKELIST
|
122
|
+
end
|
123
|
+
|
124
|
+
sourcelist = sourcetext.split(" ") if sourcelist.size == 0
|
125
|
+
throw :MAKELIST
|
126
|
+
end
|
127
|
+
|
128
|
+
sourcelist.each do |e|
|
129
|
+
# Pick some strings which is 4 or more length, is including "." character
|
130
|
+
e.chop! if e[-1, 1] == "." # Remove "." at the end of the string
|
131
|
+
e.delete!('[]()<>:;')
|
132
|
+
|
133
|
+
next unless e.size > 3
|
134
|
+
next unless e.include?(".")
|
135
|
+
next unless Sisimai::RFC1123.is_internethost(e)
|
136
|
+
foundtoken << e
|
137
|
+
end
|
138
|
+
return "" if foundtoken.size == 0
|
139
|
+
return foundtoken[0] if foundtoken.size == 1
|
140
|
+
|
141
|
+
foundtoken.each do |e|
|
142
|
+
# Returns the longest hostname
|
143
|
+
cw = e.size; next if thelongest >= cw
|
144
|
+
hostnameis = e
|
145
|
+
thelongest = cw
|
146
|
+
end
|
147
|
+
return hostnameis
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
data/lib/sisimai/rfc1894.rb
CHANGED
@@ -2,7 +2,8 @@ module Sisimai
|
|
2
2
|
# Sisimai::RFC1894 DSN field defined in RFC3464 (obsoletes RFC1894)
|
3
3
|
module RFC1894
|
4
4
|
class << self
|
5
|
-
|
5
|
+
require 'sisimai/string'
|
6
|
+
FieldNames = [
|
6
7
|
# https://tools.ietf.org/html/rfc3464#section-2.2
|
7
8
|
# Some fields of a DSN apply to all of the delivery attempts described by that DSN. At
|
8
9
|
# most, these fields may appear once in any DSN. These fields are used to correlate the
|
@@ -15,10 +16,12 @@ module Sisimai
|
|
15
16
|
# The following fields are not used in Sisimai:
|
16
17
|
# - Original-Envelope-Id
|
17
18
|
# - DSN-Gateway
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
{
|
20
|
+
'arrival-date' => ':',
|
21
|
+
'received-from-mta' => ';',
|
22
|
+
'reporting-mta' => ';',
|
23
|
+
'x-original-message-id' => '@',
|
24
|
+
},
|
22
25
|
|
23
26
|
# https://tools.ietf.org/html/rfc3464#section-2.3
|
24
27
|
# A DSN contains information about attempts to deliver a message to one or more recipi-
|
@@ -32,28 +35,31 @@ module Sisimai
|
|
32
35
|
# The following fields are not used in Sisimai:
|
33
36
|
# - Will-Retry-Until
|
34
37
|
# - Final-Log-ID
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
{
|
39
|
+
'action' => 'e',
|
40
|
+
'diagnostic-code' => ';',
|
41
|
+
'final-recipient' => ';',
|
42
|
+
'last-attempt-date' => ':',
|
43
|
+
'original-recipient' => ';',
|
44
|
+
'remote-mta' => ';',
|
45
|
+
'status' => '.',
|
46
|
+
'x-actual-recipient' => ';',
|
47
|
+
}
|
48
|
+
].freeze
|
44
49
|
|
45
50
|
CapturesOn = {
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
#'text' => %r/\A(Final-Log-ID|Original-Envelope-Id):[ ]*(.+)/,
|
51
|
+
"addr" => ["Final-Recipient", "Original-Recipient", "X-Actual-Recipient"],
|
52
|
+
"code" => ["Diagnostic-Code"],
|
53
|
+
"date" => ["Arrival-Date", "Last-Attempt-Date"],
|
54
|
+
"host" => ["Received-From-MTA", "Remote-MTA", "Reporting-MTA"],
|
55
|
+
"list" => ["Action"],
|
56
|
+
"stat" => ["Status"],
|
57
|
+
#"text" => ["X-Original-Message-ID", "Final-Log-ID", "Original-Envelope-ID"],
|
54
58
|
}.freeze
|
55
59
|
|
56
|
-
|
60
|
+
SubtypeSet = { "addr" => "RFC822", "cdoe" => "SMTP", "host" => "DNS" }.freeze
|
61
|
+
ActionList = ["failed", "delayed", "delivered", "relayed", "expanded"].freeze
|
62
|
+
Correction = { 'deliverable' => 'delivered', 'expired' => 'delayed', 'failure' => 'failed' }
|
57
63
|
FieldGroup = {
|
58
64
|
'original-recipient' => 'addr',
|
59
65
|
'final-recipient' => 'addr',
|
@@ -72,7 +78,7 @@ module Sisimai
|
|
72
78
|
def FIELDINDEX
|
73
79
|
return %w[
|
74
80
|
Action Arrival-Date Diagnostic-Code Final-Recipient Last-Attempt-Date Original-Recipient
|
75
|
-
Received-From-MTA Remote-MTA Reporting-MTA Status X-Actual-
|
81
|
+
Received-From-MTA Remote-MTA Reporting-MTA Status X-Actual-Recipient X-Original-Message-ID
|
76
82
|
]
|
77
83
|
end
|
78
84
|
|
@@ -89,7 +95,7 @@ module Sisimai
|
|
89
95
|
'original-recipient' => 'alias',
|
90
96
|
'received-from-mta' => 'lhost',
|
91
97
|
'remote-mta' => 'rhost',
|
92
|
-
'reporting-mta' => '
|
98
|
+
'reporting-mta' => 'lhost',
|
93
99
|
'status' => 'status',
|
94
100
|
'x-actual-recipient' => 'alias',
|
95
101
|
}
|
@@ -97,18 +103,34 @@ module Sisimai
|
|
97
103
|
|
98
104
|
# Check the argument matches with a field defined in RFC3464
|
99
105
|
# @param [String] argv0 A line including field and value defined in RFC3464
|
100
|
-
# @return [
|
106
|
+
# @return [Integer] 0: did not matched
|
107
|
+
# 1: Matched with per-message field
|
108
|
+
# 2: Matched with per-recipient field
|
101
109
|
# @since v4.25.0
|
102
110
|
def match(argv0 = '')
|
103
|
-
|
111
|
+
return 0 unless argv0
|
112
|
+
return 0 unless argv0.size > 0
|
113
|
+
label = Sisimai::RFC1894.label(argv0); return 0 unless label
|
114
|
+
match = 0
|
104
115
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
116
|
+
FieldNames[0].each_key do |e|
|
117
|
+
# Per-Message fields
|
118
|
+
next unless label == e
|
119
|
+
next unless argv0.include?(FieldNames[0][label])
|
120
|
+
match = 1; break
|
121
|
+
end
|
122
|
+
return match if match > 0
|
123
|
+
|
124
|
+
FieldNames[1].each_key do |e|
|
125
|
+
# Per-Recipient field
|
126
|
+
next unless label == e
|
127
|
+
next unless argv0.include?(FieldNames[1][label])
|
128
|
+
match = 2; break
|
129
|
+
end
|
130
|
+
return match
|
109
131
|
end
|
110
132
|
|
111
|
-
# Returns a field name as a
|
133
|
+
# Returns a field name as a label from the given string
|
112
134
|
# @param [String] argv0 A line including field and value defined in RFC3464
|
113
135
|
# @return [String] Field name as a label
|
114
136
|
# @since v4.25.15
|
@@ -125,43 +147,61 @@ module Sisimai
|
|
125
147
|
return nil if argv0.empty?
|
126
148
|
label = Sisimai::RFC1894.label(argv0)
|
127
149
|
group = FieldGroup[label] || ''
|
150
|
+
parts = argv0.split(":", 2); parts[1] = parts[1].nil? ? "" : Sisimai::String.sweep(parts[1])
|
128
151
|
|
129
152
|
return nil if group.empty?
|
130
153
|
return nil unless CapturesOn[group]
|
131
154
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
if
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
table[
|
149
|
-
table[2] = group == 'host' ? cv[3].downcase : cv[3]
|
150
|
-
table[2] = '' if table[2] =~ /\A\s+\z/ # Remote-MTA: dns;
|
155
|
+
# Try to match with each pattern of Per-Message field, Per-Recipient field
|
156
|
+
# - 0: Field-Name
|
157
|
+
# - 1: Sub Type: RFC822, DNS, X-Unix, and so on)
|
158
|
+
# - 2: Value
|
159
|
+
# - 3: Field Group(addr, code, date, host, stat, text)
|
160
|
+
# - 4: Comment
|
161
|
+
table = [label, "", "", group, ""]
|
162
|
+
|
163
|
+
if group == 'addr' || group == 'code' || group == 'host'
|
164
|
+
# - Final-Recipient: RFC822; kijitora@nyaan.jp
|
165
|
+
# - Diagnostic-Code: SMTP; 550 5.1.1 <kijitora@example.jp>... User Unknown
|
166
|
+
# - Remote-MTA: DNS; mx.example.jp
|
167
|
+
if parts[1].include?(";")
|
168
|
+
# There is a valid sub type (including ";")
|
169
|
+
v = parts[1].split(";", 2)
|
170
|
+
table[1] = Sisimai::String.sweep(v[0]).upcase if v.size > 0
|
171
|
+
table[2] = Sisimai::String.sweep(v[1]) if v.size > 1
|
151
172
|
else
|
152
|
-
# -
|
153
|
-
|
154
|
-
table[1] =
|
155
|
-
table[2] = group == 'date' ? cv[2] : cv[2].downcase
|
156
|
-
|
157
|
-
# Correct invalid value in Action field:
|
158
|
-
break unless group == 'list'
|
159
|
-
break unless Correction[:action][table[2]]
|
160
|
-
table[2] = Correction[:action][table[2]]
|
173
|
+
# There is no sub type like "Diagnostic-Code: 550 5.1.1 <kijitora@example.jp>..."
|
174
|
+
table[2] = Sisimai::String.sweep(parts[1])
|
175
|
+
table[1] = SubtypeSet[group] || ""
|
161
176
|
end
|
162
|
-
|
177
|
+
table[2] = table[2].downcase if group == "host"
|
178
|
+
table[2] = "" if table[2] =~ /\A\s+\z/
|
179
|
+
|
180
|
+
elsif group == "list"
|
181
|
+
# Action: failed
|
182
|
+
# Check that the value is an available value defined in "ActionList" or not.
|
183
|
+
# When the value is invalid, convert to an available value defined in "Correction"
|
184
|
+
v = parts[1].downcase
|
185
|
+
table[2] = v if ActionList.any? { |a| v == a }
|
186
|
+
table[2] = Correction[v] if table[2].empty?
|
187
|
+
|
188
|
+
else
|
189
|
+
# Other groups such as Status:, Arrival-Date:, or X-Original-Message-ID:.
|
190
|
+
# There is no ";" character in the field.
|
191
|
+
# - Status: 5.2.2
|
192
|
+
# - Arrival-Date: Mon, 21 May 2018 16:09:59 +0900
|
193
|
+
table[2] = group == "date" ? parts[1] : parts[1].downcase
|
163
194
|
end
|
164
|
-
|
195
|
+
|
196
|
+
if Sisimai::String.aligned(table[2], [" (", ")"])
|
197
|
+
# Extract text enclosed in parentheses as comments
|
198
|
+
# Reporting-MTA: dns; mr21p30im-asmtp004.me.example.com (tcp-daemon)
|
199
|
+
p1 = table[2].index(" (")
|
200
|
+
p2 = table[2].index(")")
|
201
|
+
table[4] = table[2][p1 + 2, p2 - p1 - 2]
|
202
|
+
table[2] = table[2][0, p1]
|
203
|
+
end
|
204
|
+
|
165
205
|
return table
|
166
206
|
end
|
167
207
|
|
data/lib/sisimai/rfc2045.rb
CHANGED
@@ -277,6 +277,7 @@ module Sisimai
|
|
277
277
|
iso2022set = %r/charset=["']?(iso-2022-[-a-z0-9]+)['"]?\b/
|
278
278
|
multiparts = levelout(argv0, argv1)
|
279
279
|
flattenout = ''
|
280
|
+
delimiters = ["/delivery-status", "/rfc822", "/feedback-report", "/partial"]
|
280
281
|
|
281
282
|
while e = multiparts.shift do
|
282
283
|
# Pick only the following parts Sisimai::Lhost will use, and decode each part
|
@@ -352,7 +353,7 @@ module Sisimai
|
|
352
353
|
bodystring << bodyinside
|
353
354
|
end
|
354
355
|
|
355
|
-
if
|
356
|
+
if delimiters.any? { |a| mediatypev.include?(a) }
|
356
357
|
# Add Content-Type: header of each part (will be used as a delimiter at Sisimai::Lhost)
|
357
358
|
# into the body inside when the value of Content-Type: field is message/delivery-status,
|
358
359
|
# message/rfc822, or text/rfc822-headers
|