sisimai 4.25.17 → 5.0.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/.travis.yml +3 -3
- data/ANALYTICAL-PRECISION +2 -2
- data/Benchmarks.mk +3 -3
- data/CONTRIBUTING +1 -1
- data/ChangeLog.md +406 -407
- data/Developers.mk +5 -6
- data/Gemfile +1 -1
- data/Makefile +12 -12
- data/README-JA.md +142 -94
- data/README.md +282 -150
- data/Rakefile +9 -3
- data/Repository.mk +2 -3
- data/lib/sisimai/address.rb +118 -74
- data/lib/sisimai/arf.rb +84 -82
- data/lib/sisimai/datetime.rb +5 -52
- data/lib/sisimai/{data → fact}/json.rb +7 -9
- data/lib/sisimai/fact/yaml.rb +31 -0
- data/lib/sisimai/fact.rb +468 -0
- data/lib/sisimai/lhost/activehunter.rb +12 -14
- data/lib/sisimai/lhost/amavis.rb +11 -14
- data/lib/sisimai/lhost/amazonses.rb +37 -41
- data/lib/sisimai/lhost/amazonworkmail.rb +15 -18
- data/lib/sisimai/lhost/aol.rb +12 -14
- data/lib/sisimai/lhost/apachejames.rb +19 -21
- data/lib/sisimai/lhost/barracuda.rb +10 -12
- data/lib/sisimai/lhost/bigfoot.rb +21 -21
- data/lib/sisimai/lhost/biglobe.rb +15 -16
- data/lib/sisimai/lhost/courier.rb +20 -20
- data/lib/sisimai/lhost/domino.rb +23 -19
- data/lib/sisimai/lhost/einsundeins.rb +20 -16
- data/lib/sisimai/lhost/exchange2003.rb +30 -29
- data/lib/sisimai/lhost/exchange2007.rb +70 -58
- data/lib/sisimai/lhost/exim.rb +175 -161
- data/lib/sisimai/lhost/ezweb.rb +31 -56
- data/lib/sisimai/lhost/facebook.rb +21 -33
- data/lib/sisimai/lhost/fml.rb +43 -48
- data/lib/sisimai/lhost/gmail.rb +29 -29
- data/lib/sisimai/lhost/gmx.rb +18 -17
- data/lib/sisimai/lhost/googlegroups.rb +9 -10
- data/lib/sisimai/lhost/gsuite.rb +21 -27
- data/lib/sisimai/lhost/imailserver.rb +25 -39
- data/lib/sisimai/lhost/interscanmss.rb +28 -31
- data/lib/sisimai/lhost/kddi.rb +22 -28
- data/lib/sisimai/lhost/mailfoundry.rb +11 -12
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
- data/lib/sisimai/lhost/mailru.rb +33 -27
- data/lib/sisimai/lhost/mcafee.rb +21 -31
- data/lib/sisimai/lhost/messagelabs.rb +17 -20
- data/lib/sisimai/lhost/messagingserver.rb +40 -37
- data/lib/sisimai/lhost/mfilter.rb +15 -16
- data/lib/sisimai/lhost/mxlogic.rb +24 -23
- data/lib/sisimai/lhost/notes.rb +17 -17
- data/lib/sisimai/lhost/office365.rb +63 -27
- data/lib/sisimai/lhost/opensmtpd.rb +12 -13
- data/lib/sisimai/lhost/outlook.rb +12 -15
- data/lib/sisimai/lhost/postfix.rb +179 -129
- data/lib/sisimai/lhost/powermta.rb +12 -14
- data/lib/sisimai/lhost/qmail.rb +44 -47
- data/lib/sisimai/lhost/receivingses.rb +15 -20
- data/lib/sisimai/lhost/sendgrid.rb +34 -32
- data/lib/sisimai/lhost/sendmail.rb +66 -53
- data/lib/sisimai/lhost/surfcontrol.rb +19 -19
- data/lib/sisimai/lhost/v5sendmail.rb +45 -39
- data/lib/sisimai/lhost/verizon.rb +35 -39
- data/lib/sisimai/lhost/x1.rb +18 -17
- data/lib/sisimai/lhost/x2.rb +17 -14
- data/lib/sisimai/lhost/x3.rb +19 -19
- data/lib/sisimai/lhost/x4.rb +72 -57
- data/lib/sisimai/lhost/x5.rb +17 -19
- data/lib/sisimai/lhost/x6.rb +41 -17
- data/lib/sisimai/lhost/yahoo.rb +17 -16
- data/lib/sisimai/lhost/yandex.rb +16 -20
- data/lib/sisimai/lhost/zoho.rb +16 -15
- data/lib/sisimai/lhost.rb +8 -10
- data/lib/sisimai/mail/maildir.rb +1 -3
- data/lib/sisimai/mail/mbox.rb +3 -4
- data/lib/sisimai/mail/memory.rb +0 -1
- data/lib/sisimai/mail/stdin.rb +1 -3
- data/lib/sisimai/mail.rb +3 -7
- data/lib/sisimai/mda.rb +28 -42
- data/lib/sisimai/message.rb +435 -325
- data/lib/sisimai/order.rb +5 -5
- data/lib/sisimai/reason/authfailure.rb +64 -0
- data/lib/sisimai/reason/badreputation.rb +53 -0
- data/lib/sisimai/reason/blocked.rb +94 -160
- data/lib/sisimai/reason/contenterror.rb +8 -9
- data/lib/sisimai/reason/delivered.rb +4 -6
- data/lib/sisimai/reason/exceedlimit.rb +10 -12
- data/lib/sisimai/reason/expired.rb +6 -8
- data/lib/sisimai/reason/feedback.rb +2 -3
- data/lib/sisimai/reason/filtered.rb +17 -19
- data/lib/sisimai/reason/hasmoved.rb +9 -10
- data/lib/sisimai/reason/hostunknown.rb +15 -15
- data/lib/sisimai/reason/mailboxfull.rb +10 -12
- data/lib/sisimai/reason/mailererror.rb +18 -20
- data/lib/sisimai/reason/mesgtoobig.rb +9 -11
- data/lib/sisimai/reason/networkerror.rb +5 -8
- data/lib/sisimai/reason/norelaying.rb +8 -11
- data/lib/sisimai/reason/notaccept.rb +13 -14
- data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
- data/lib/sisimai/reason/onhold.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +14 -12
- data/lib/sisimai/reason/rejected.rb +26 -24
- data/lib/sisimai/reason/requireptr.rb +69 -0
- data/lib/sisimai/reason/securityerror.rb +33 -36
- data/lib/sisimai/reason/spamdetected.rb +114 -147
- data/lib/sisimai/reason/speeding.rb +49 -0
- data/lib/sisimai/reason/suspend.rb +11 -11
- data/lib/sisimai/reason/syntaxerror.rb +11 -10
- data/lib/sisimai/reason/systemerror.rb +7 -9
- data/lib/sisimai/reason/systemfull.rb +7 -8
- data/lib/sisimai/reason/toomanyconn.rb +9 -11
- data/lib/sisimai/reason/undefined.rb +2 -3
- data/lib/sisimai/reason/userunknown.rb +129 -146
- data/lib/sisimai/reason/vacation.rb +3 -4
- data/lib/sisimai/reason/virusdetected.rb +10 -11
- data/lib/sisimai/reason.rb +59 -64
- data/lib/sisimai/rfc1894.rb +55 -28
- data/lib/sisimai/rfc2045.rb +373 -0
- data/lib/sisimai/rfc3464.rb +250 -308
- data/lib/sisimai/rfc3834.rb +42 -45
- data/lib/sisimai/rfc5322.rb +75 -100
- data/lib/sisimai/rfc5965.rb +31 -0
- data/lib/sisimai/rhost/cox.rb +5 -6
- data/lib/sisimai/rhost/franceptt.rb +6 -8
- data/lib/sisimai/rhost/godaddy.rb +12 -12
- data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
- data/lib/sisimai/rhost/iua.rb +9 -10
- data/lib/sisimai/rhost/kddi.rb +6 -8
- data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
- data/lib/sisimai/rhost/mimecast.rb +42 -40
- data/lib/sisimai/rhost/nttdocomo.rb +13 -18
- data/lib/sisimai/rhost/spectrum.rb +10 -12
- data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
- data/lib/sisimai/rhost.rb +23 -31
- data/lib/sisimai/smtp/command.rb +59 -0
- data/lib/sisimai/smtp/error.rb +4 -7
- data/lib/sisimai/smtp/reply.rb +161 -74
- data/lib/sisimai/smtp/status.rb +504 -393
- data/lib/sisimai/smtp/transcript.rb +124 -0
- data/lib/sisimai/smtp.rb +0 -1
- data/lib/sisimai/string.rb +74 -5
- data/lib/sisimai/time.rb +1 -2
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +35 -21
- data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
- data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
- data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
- data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
- data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
- data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
- data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
- data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
- data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
- data/sisimai-java.gemspec +1 -1
- data/sisimai.gemspec +1 -1
- metadata +41 -21
- data/.rspec +0 -2
- data/lib/sisimai/data/yaml.rb +0 -33
- data/lib/sisimai/data.rb +0 -411
- data/lib/sisimai/mime.rb +0 -456
- data/set-of-emails/maildir/mac/reported-from-nick4tech-san-01.eml +0 -6
- /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
data/lib/sisimai/lhost/qmail.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
module Sisimai::Lhost
|
2
|
-
# Sisimai::Lhost::Qmail parses a bounce email which created by qmail.
|
3
|
-
#
|
2
|
+
# Sisimai::Lhost::Qmail parses a bounce email which created by qmail. Methods in the module are called
|
3
|
+
# from only Sisimai::Message.
|
4
4
|
module Qmail
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/Lhost/qmail.pm
|
7
6
|
require 'sisimai/lhost'
|
8
7
|
|
9
8
|
Indicators = Sisimai::Lhost.INDICATORS
|
10
|
-
|
9
|
+
Boundaries = ['--- Below this line is a copy of the message.'].freeze
|
11
10
|
StartingOf = {
|
12
11
|
# qmail-remote.c:248| if (code >= 500) {
|
13
12
|
# qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n");
|
@@ -16,45 +15,33 @@ module Sisimai::Lhost
|
|
16
15
|
#
|
17
16
|
# Characters: K,Z,D in qmail-qmqpc.c, qmail-send.c, qmail-rspawn.c
|
18
17
|
# K = success, Z = temporary error, D = permanent error
|
19
|
-
message: ['Hi. This is the qmail'],
|
20
18
|
error: ['Remote host said:'],
|
19
|
+
message: ['Hi. This is the qmail'],
|
20
|
+
rhost: ['Giving up on ', 'Connected to ', 'remote host '],
|
21
21
|
}.freeze
|
22
|
-
|
23
|
-
ReSMTP = {
|
22
|
+
CommandSet = {
|
24
23
|
# Error text regular expressions which defined in qmail-remote.c
|
25
24
|
# qmail-remote.c:225| if (smtpcode() != 220) quit("ZConnected to "," but greeting failed");
|
26
|
-
'conn' =>
|
25
|
+
'conn' => [' but greeting failed.'],
|
27
26
|
# qmail-remote.c:231| if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected");
|
28
|
-
'ehlo' =>
|
27
|
+
'ehlo' => [' but my name was rejected.'],
|
29
28
|
# qmail-remote.c:238| if (code >= 500) quit("DConnected to "," but sender was rejected");
|
30
29
|
# reason = rejected
|
31
|
-
'mail' =>
|
30
|
+
'mail' => [' but sender was rejected.'],
|
32
31
|
# qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n");
|
33
32
|
# qmail-remote.c:253| out("s"); outhost(); out(" does not like recipient.\n");
|
34
33
|
# reason = userunknown
|
35
|
-
'rcpt' =>
|
34
|
+
'rcpt' => [' does not like recipient.'],
|
36
35
|
# qmail-remote.c:265| if (code >= 500) quit("D"," failed on DATA command");
|
37
36
|
# qmail-remote.c:266| if (code >= 400) quit("Z"," failed on DATA command");
|
38
37
|
# qmail-remote.c:271| if (code >= 500) quit("D"," failed after I sent the message");
|
39
38
|
# qmail-remote.c:272| if (code >= 400) quit("Z"," failed after I sent the message");
|
40
|
-
'data' =>
|
41
|
-
(?:Error:)?[^ ]+[ ]failed[ ]on[ ]DATA[ ]command[.]
|
42
|
-
|(?:Error:)?[^ ]+[ ]failed[ ]after[ ]I[ ]sent[ ]the[ ]message[.]
|
43
|
-
)
|
44
|
-
}x,
|
39
|
+
'data' => [' failed on DATA command', ' failed after I sent the message'],
|
45
40
|
}.freeze
|
46
|
-
# qmail-remote.c:261| if (!flagbother) quit("DGiving up on ","");
|
47
|
-
ReHost = %r{(?:
|
48
|
-
Giving[ ]up[ ]on[ ]([^ ]+[0-9a-zA-Z])[.]?\z
|
49
|
-
|Connected[ ]to[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ]
|
50
|
-
|remote[ ]host[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ]said:
|
51
|
-
)
|
52
|
-
}x
|
53
41
|
|
54
42
|
# qmail-send.c:922| ... (&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem();
|
55
43
|
HasExpired = 'this message has been in the queue too long.'
|
56
|
-
|
57
|
-
ReIsOnHold = %r/\A[^ ]+ does not like recipient[.][ ]+.+this message has been in the queue too long[.]\z/
|
44
|
+
OnHoldPair = [' does not like recipient.', 'this message has been in the queue too long.'].freeze
|
58
45
|
FailOnLDAP = {
|
59
46
|
# qmail-ldap-1.03-20040101.patch:19817 - 19866
|
60
47
|
'suspend' => ['Mailaddress is administrative?le?y disabled'], # 5.2.1
|
@@ -104,27 +91,31 @@ module Sisimai::Lhost
|
|
104
91
|
# @param [String] mbody Message body of a bounce email
|
105
92
|
# @return [Hash] Bounce data list and message/rfc822 part
|
106
93
|
# @return [Nil] it failed to parse or the arguments are missing
|
107
|
-
def
|
94
|
+
def inquire(mhead, mbody)
|
108
95
|
# Pre process email headers and the body part of the message which generated
|
109
96
|
# by qmail, see https://cr.yp.to/qmail.html
|
110
97
|
# e.g.) Received: (qmail 12345 invoked for bounce); 29 Apr 2009 12:34:56 -0000
|
111
98
|
# Subject: failure notice
|
112
|
-
tryto =
|
99
|
+
tryto = [['(qmail ', 'invoked for bounce)'], ['(qmail ', 'invoked from ', 'network)']]
|
113
100
|
match = 0
|
114
101
|
match += 1 if mhead['subject'] == 'failure notice'
|
115
|
-
|
102
|
+
mhead['received'].each do |e|
|
103
|
+
# Received: (qmail 2222 invoked for bounce);29 Apr 2017 23:34:45 +0900
|
104
|
+
# Received: (qmail 2202 invoked from network); 29 Apr 2018 00:00:00 +0900
|
105
|
+
match += 1 if tryto.any? { |a| Sisimai::String.aligned(e, a) }
|
106
|
+
end
|
116
107
|
return nil unless match > 0
|
117
108
|
|
118
109
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
119
|
-
|
120
|
-
bodyslices =
|
110
|
+
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
111
|
+
bodyslices = emailparts[0].split("\n")
|
121
112
|
readcursor = 0 # (Integer) Points the current cursor position
|
122
113
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
123
114
|
v = nil
|
124
115
|
|
125
116
|
while e = bodyslices.shift do
|
126
|
-
# Read error messages and delivery status lines from the head of the email
|
127
|
-
#
|
117
|
+
# Read error messages and delivery status lines from the head of the email to the previous
|
118
|
+
# line of the beginning of the original message.
|
128
119
|
if readcursor == 0
|
129
120
|
# Beginning of the bounce message or delivery status part
|
130
121
|
readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
|
@@ -139,14 +130,14 @@ module Sisimai::Lhost
|
|
139
130
|
# Giving up on 192.0.2.153.
|
140
131
|
v = dscontents[-1]
|
141
132
|
|
142
|
-
if
|
133
|
+
if e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>', ':'])
|
143
134
|
# <kijitora@example.jp>:
|
144
135
|
if v['recipient']
|
145
136
|
# There are multiple recipient addresses in the message body.
|
146
137
|
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
147
138
|
v = dscontents[-1]
|
148
139
|
end
|
149
|
-
v['recipient'] =
|
140
|
+
v['recipient'] = Sisimai::Address.s3s4(e[e.index('<'), e.size])
|
150
141
|
recipients += 1
|
151
142
|
|
152
143
|
elsif dscontents.size == recipients
|
@@ -157,8 +148,15 @@ module Sisimai::Lhost
|
|
157
148
|
v['alterrors'] = e if e.start_with?(StartingOf[:error][0])
|
158
149
|
|
159
150
|
next if v['rhost']
|
160
|
-
|
161
|
-
|
151
|
+
StartingOf[:rhost].each do |r|
|
152
|
+
# Find a remote host name
|
153
|
+
p1 = e.index(r); next unless p1
|
154
|
+
cm = r.size
|
155
|
+
p2 = e.index(' ', p1 + cm + 1) || p2 = e.rindex('.')
|
156
|
+
|
157
|
+
v['rhost'] = e[p1 + cm, p2 - p1 - cm]
|
158
|
+
break
|
159
|
+
end
|
162
160
|
end
|
163
161
|
end
|
164
162
|
return nil unless recipients > 0
|
@@ -169,17 +167,16 @@ module Sisimai::Lhost
|
|
169
167
|
|
170
168
|
unless e['command']
|
171
169
|
# Get the SMTP command name for the session
|
172
|
-
|
170
|
+
CommandSet.each_key do |r|
|
173
171
|
# Verify each regular expression of SMTP commands
|
174
|
-
next unless e['diagnosis']
|
172
|
+
next unless CommandSet[r].any? { |a| e['diagnosis'].include?(a) }
|
175
173
|
e['command'] = r.upcase
|
176
174
|
break
|
177
175
|
end
|
178
176
|
|
179
|
-
|
180
|
-
#
|
181
|
-
|
182
|
-
e['command'] ||= ''
|
177
|
+
if e['diagnosis'].include?('Sorry, no SMTP connection got far enough; most progress was ')
|
178
|
+
# Get the last SMTP command:from the error message
|
179
|
+
e['command'] ||= Sisimai::SMTP::Command.find(e['diagnosis']) || ''
|
183
180
|
end
|
184
181
|
end
|
185
182
|
|
@@ -193,9 +190,8 @@ module Sisimai::Lhost
|
|
193
190
|
e['reason'] = 'blocked'
|
194
191
|
else
|
195
192
|
# Try to match with each error message in the table
|
196
|
-
if e['diagnosis']
|
197
|
-
# To decide the reason require pattern match with
|
198
|
-
# Sisimai::Reason::* modules
|
193
|
+
if Sisimai::String.aligned(e['diagnosis'], OnHoldPair)
|
194
|
+
# To decide the reason require pattern match with Sisimai::Reason::* modules
|
199
195
|
e['reason'] = 'onhold'
|
200
196
|
else
|
201
197
|
MessagesOf.each_key do |r|
|
@@ -227,10 +223,11 @@ module Sisimai::Lhost
|
|
227
223
|
end
|
228
224
|
end
|
229
225
|
|
230
|
-
e['
|
226
|
+
e['command'] ||= Sisimai::SMTP::Command.find(e['diagnosis'])
|
227
|
+
e['status'] = Sisimai::SMTP::Status.find(e['diagnosis']) || ''
|
231
228
|
end
|
232
229
|
|
233
|
-
return { 'ds' => dscontents, 'rfc822' =>
|
230
|
+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
234
231
|
end
|
235
232
|
def description; return 'qmail'; end
|
236
233
|
end
|
@@ -1,15 +1,13 @@
|
|
1
1
|
module Sisimai::Lhost
|
2
|
-
# Sisimai::Lhost::ReceivingSES parses a bounce email which created
|
3
|
-
#
|
4
|
-
# only Sisimai::Message.
|
2
|
+
# Sisimai::Lhost::ReceivingSES parses a bounce email which created by Amazon Simple Email Service.
|
3
|
+
# Methods in the module are called from only Sisimai::Message.
|
5
4
|
module ReceivingSES
|
6
5
|
class << self
|
7
|
-
# Imported from p5-Sisimail/lib/Sisimai/Lhost/ReceivingSES.pm
|
8
6
|
require 'sisimai/lhost'
|
9
7
|
|
10
8
|
# https://aws.amazon.com/ses/
|
11
9
|
Indicators = Sisimai::Lhost.INDICATORS
|
12
|
-
|
10
|
+
Boundaries = ['Content-Type: text/rfc822-headers'].freeze
|
13
11
|
StartingOf = { message: ['This message could not be delivered.'] }.freeze
|
14
12
|
MessagesOf = {
|
15
13
|
# The followings are error messages in Rule sets/*/Actions/Template
|
@@ -24,26 +22,25 @@ module Sisimai::Lhost
|
|
24
22
|
# @param [String] mbody Message body of a bounce email
|
25
23
|
# @return [Hash] Bounce data list and message/rfc822 part
|
26
24
|
# @return [Nil] it failed to parse or the arguments are missing
|
27
|
-
def
|
25
|
+
def inquire(mhead, mbody)
|
28
26
|
# X-SES-Outgoing: 2015.10.01-54.240.27.7
|
29
27
|
# Feedback-ID: 1.us-west-2.HX6/J9OVlHTadQhEu1+wdF9DBj6n6Pa9sW5Y/0pSOi8=:AmazonSES
|
30
28
|
return nil unless mhead['x-ses-outgoing']
|
31
29
|
|
32
|
-
require 'sisimai/rfc1894'
|
33
30
|
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
34
31
|
permessage = {} # (Hash) Store values of each Per-Message field
|
35
32
|
|
36
33
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
37
|
-
|
38
|
-
bodyslices =
|
34
|
+
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
35
|
+
bodyslices = emailparts[0].split("\n")
|
39
36
|
readslices = ['']
|
40
37
|
readcursor = 0 # (Integer) Points the current cursor position
|
41
38
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
42
39
|
v = nil
|
43
40
|
|
44
41
|
while e = bodyslices.shift do
|
45
|
-
# Read error messages and delivery status lines from the head of the email
|
46
|
-
#
|
42
|
+
# Read error messages and delivery status lines from the head of the email to the previous
|
43
|
+
# line of the beginning of the original message.
|
47
44
|
readslices << e # Save the current line for the next loop
|
48
45
|
|
49
46
|
if readcursor == 0
|
@@ -84,14 +81,14 @@ module Sisimai::Lhost
|
|
84
81
|
next unless fieldtable[o[0]]
|
85
82
|
v[fieldtable[o[0]]] = o[2]
|
86
83
|
|
87
|
-
next unless f
|
84
|
+
next unless f
|
88
85
|
permessage[fieldtable[o[0]]] = o[2]
|
89
86
|
end
|
90
87
|
else
|
91
88
|
# Continued line of the value of Diagnostic-Code field
|
92
89
|
next unless readslices[-2].start_with?('Diagnostic-Code:')
|
93
|
-
next unless
|
94
|
-
v['diagnosis'] << ' ' <<
|
90
|
+
next unless e.start_with?(' ')
|
91
|
+
v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
|
95
92
|
readslices[-1] = 'Diagnostic-Code: ' << e
|
96
93
|
end
|
97
94
|
end
|
@@ -106,11 +103,9 @@ module Sisimai::Lhost
|
|
106
103
|
if e['status'].to_s.start_with?('5.0.0', '5.1.0', '4.0.0', '4.1.0')
|
107
104
|
# Get other D.S.N. value from the error message
|
108
105
|
errormessage = e['diagnosis']
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
errormessage = cv[1]
|
113
|
-
end
|
106
|
+
p1 = e['diagnosis'].index("-'"); p1 = e['diagnosis'].index('-"') unless p1
|
107
|
+
p2 = e['diagnosis'].rindex("' "); p2 = e['diagnosis'].rindex('" ') unless p2
|
108
|
+
errormessage = e['diagnosis'][p1 + 2, p2 - p1 - 2] if p1 && p2
|
114
109
|
e['status'] = Sisimai::SMTP::Status.find(errormessage) || e['status']
|
115
110
|
end
|
116
111
|
|
@@ -123,7 +118,7 @@ module Sisimai::Lhost
|
|
123
118
|
e['reason'] ||= Sisimai::SMTP::Status.name(e['status']) || ''
|
124
119
|
end
|
125
120
|
|
126
|
-
return { 'ds' => dscontents, 'rfc822' =>
|
121
|
+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
127
122
|
end
|
128
123
|
def description; return 'Amazon SES(Receiving): https://aws.amazon.com/ses/'; end
|
129
124
|
end
|
@@ -1,13 +1,12 @@
|
|
1
1
|
module Sisimai::Lhost
|
2
|
-
# Sisimai::Lhost::SendGrid parses a bounce email which created by
|
3
|
-
#
|
2
|
+
# Sisimai::Lhost::SendGrid parses a bounce email which created by SendGrid. Methods in the module
|
3
|
+
# are called from only Sisimai::Message.
|
4
4
|
module SendGrid
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/Lhost/SendGrid.pm
|
7
6
|
require 'sisimai/lhost'
|
8
7
|
|
9
8
|
Indicators = Sisimai::Lhost.INDICATORS
|
10
|
-
|
9
|
+
Boundaries = ['Content-Type: message/rfc822'].freeze
|
11
10
|
StartingOf = { message: ['This is an automatically generated message from SendGrid.'] }.freeze
|
12
11
|
|
13
12
|
# Parse bounce messages from SendGrid
|
@@ -15,29 +14,29 @@ module Sisimai::Lhost
|
|
15
14
|
# @param [String] mbody Message body of a bounce email
|
16
15
|
# @return [Hash] Bounce data list and message/rfc822 part
|
17
16
|
# @return [Nil] it failed to parse or the arguments are missing
|
18
|
-
def
|
17
|
+
def inquire(mhead, mbody)
|
19
18
|
# Return-Path: <apps@sendgrid.net>
|
20
19
|
# X-Mailer: MIME-tools 5.502 (Entity 5.502)
|
21
20
|
return nil unless mhead['return-path']
|
22
21
|
return nil unless mhead['return-path'] == '<apps@sendgrid.net>'
|
23
22
|
return nil unless mhead['subject'] == 'Undelivered Mail Returned to Sender'
|
24
23
|
|
25
|
-
require 'sisimai/
|
24
|
+
require 'sisimai/smtp/command'
|
26
25
|
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
27
26
|
permessage = {} # (Hash) Store values of each Per-Message field
|
28
27
|
|
29
28
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
30
|
-
|
31
|
-
bodyslices =
|
29
|
+
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
30
|
+
bodyslices = emailparts[0].split("\n")
|
32
31
|
readslices = ['']
|
33
32
|
readcursor = 0 # (Integer) Points the current cursor position
|
34
33
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
35
|
-
|
34
|
+
thecommand = '' # (String) SMTP Command name begin with the string '>>>'
|
36
35
|
v = nil
|
37
36
|
|
38
37
|
while e = bodyslices.shift do
|
39
|
-
# Read error messages and delivery status lines from the head of the email
|
40
|
-
#
|
38
|
+
# Read error messages and delivery status lines from the head of the email to the previous
|
39
|
+
# line of the beginning of the original message.
|
41
40
|
readslices << e # Save the current line for the next loop
|
42
41
|
|
43
42
|
if readcursor == 0
|
@@ -57,8 +56,7 @@ module Sisimai::Lhost
|
|
57
56
|
# Fallback code for empty value or invalid formatted value
|
58
57
|
# - Status: (empty)
|
59
58
|
# - Diagnostic-Code: 550 5.1.1 ... (No "diagnostic-type" sub field)
|
60
|
-
|
61
|
-
v['diagnosis'] = cv[1]
|
59
|
+
v['diagnosis'] = e[e.index(':') + 2, e.size] if e.start_with?('Diagnostic-Code: ')
|
62
60
|
next
|
63
61
|
end
|
64
62
|
|
@@ -84,29 +82,37 @@ module Sisimai::Lhost
|
|
84
82
|
v['diagnosis'] = o[2]
|
85
83
|
elsif o[-1] == 'date'
|
86
84
|
# Arrival-Date: 2012-12-31 23-59-59
|
87
|
-
next unless
|
88
|
-
|
89
|
-
|
90
|
-
|
85
|
+
next unless e.start_with?('Arrival-Date: ')
|
86
|
+
cf = e[e.index(': ') + 2, e.size].split(' '); next unless cf.size == 2
|
87
|
+
cw = cf[0].split('-'); next unless cw.size == 3
|
88
|
+
ce = cf[1].split('-'); next unless ce.size == 3
|
89
|
+
|
90
|
+
o[1] << 'Thu, ' << cw[2] + ' '
|
91
|
+
o[1] << Sisimai::DateTime.monthname(false)[cw[1].to_i - 1]
|
92
|
+
o[1] << ' ' << cw[0] + ' ' << ce.join(':')
|
91
93
|
o[1] << ' ' << Sisimai::DateTime.abbr2tz('CDT')
|
92
94
|
else
|
93
95
|
# Other DSN fields defined in RFC3464
|
94
96
|
next unless fieldtable[o[0]]
|
95
97
|
v[fieldtable[o[0]]] = o[2]
|
96
98
|
|
97
|
-
next unless f
|
99
|
+
next unless f
|
98
100
|
permessage[fieldtable[o[0]]] = o[2]
|
99
101
|
end
|
100
102
|
else
|
101
103
|
# The line does not begin with a DSN field defined in RFC3464
|
102
|
-
if cv =
|
104
|
+
if cv = Sisimai::SMTP::Command.find(e)
|
103
105
|
# in RCPT TO, in MAIL FROM, end of DATA
|
104
|
-
|
106
|
+
thecommand = cv
|
107
|
+
elsif e.start_with?('Diagnostic-Code: ')
|
108
|
+
# Diagnostic-Code: 550 5.1.1 <kijitora@example.jp>... User Unknown
|
109
|
+
v['diagnosis'] = e[e.index(':') + 2, e.size]
|
105
110
|
else
|
106
111
|
# Continued line of the value of Diagnostic-Code field
|
107
112
|
next unless readslices[-2].start_with?('Diagnostic-Code:')
|
108
|
-
next unless
|
109
|
-
v['diagnosis']
|
113
|
+
next unless e.start_with?(' ')
|
114
|
+
v['diagnosis'] ||= ''
|
115
|
+
v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
|
110
116
|
readslices[-1] = 'Diagnostic-Code: ' << e
|
111
117
|
end
|
112
118
|
end
|
@@ -116,14 +122,11 @@ module Sisimai::Lhost
|
|
116
122
|
dscontents.each do |e|
|
117
123
|
# Get the value of SMTP status code as a pseudo D.S.N.
|
118
124
|
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
119
|
-
|
120
|
-
|
121
|
-
e['status'] = cv[1] + '.0.0'
|
122
|
-
end
|
125
|
+
e['replycode'] = Sisimai::SMTP::Reply.find(e['diagnosis']) || ''
|
126
|
+
e['status'] = e['replycode'][0, 1] + '.0.0' if e['replycode'].size == 3
|
123
127
|
|
124
128
|
if e['status'] == '5.0.0' || e['status'] == '4.0.0'
|
125
|
-
# Get the value of D.S.N. from the error message or the value of
|
126
|
-
# Diagnostic-Code header.
|
129
|
+
# Get the value of D.S.N. from the error message or the value of Diagnostic-Code header.
|
127
130
|
e['status'] = Sisimai::SMTP::Status.find(e['diagnosis']) || e['status']
|
128
131
|
end
|
129
132
|
|
@@ -131,17 +134,16 @@ module Sisimai::Lhost
|
|
131
134
|
# Action: expired
|
132
135
|
e['reason'] = 'expired'
|
133
136
|
if !e['status'] || e['status'].end_with?('.0.0')
|
134
|
-
# Set pseudo Status code value if the value of Status is not
|
135
|
-
# defined or 4.0.0 or 5.0.0.
|
137
|
+
# Set pseudo Status code value if the value of Status is not defined or 4.0.0 or 5.0.0.
|
136
138
|
e['status'] = Sisimai::SMTP::Status.code('expired') || e['status']
|
137
139
|
end
|
138
140
|
end
|
139
141
|
|
140
142
|
e['lhost'] ||= permessage['rhost']
|
141
|
-
e['command'] =
|
143
|
+
e['command'] = thecommand
|
142
144
|
end
|
143
145
|
|
144
|
-
return { 'ds' => dscontents, 'rfc822' =>
|
146
|
+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
145
147
|
end
|
146
148
|
def description; return 'SendGrid: https://sendgrid.com/'; end
|
147
149
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module Sisimai::Lhost
|
2
|
-
# Sisimai::Lhost::Sendmail parses a bounce email which created by
|
3
|
-
#
|
2
|
+
# Sisimai::Lhost::Sendmail parses a bounce email which created by v8 Sendmail. Methods in the module
|
3
|
+
# are called from only Sisimai::Message.
|
4
4
|
module Sendmail
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/Lhost/Sendmail.pm
|
7
6
|
require 'sisimai/lhost'
|
7
|
+
require 'sisimai/smtp/reply'
|
8
|
+
require 'sisimai/smtp/status'
|
9
|
+
require 'sisimai/smtp/command'
|
8
10
|
|
9
11
|
Indicators = Sisimai::Lhost.INDICATORS
|
10
|
-
|
12
|
+
Boundaries = ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'].freeze
|
11
13
|
StartingOf = {
|
12
14
|
# Error text regular expressions which defined in sendmail/savemail.c
|
13
15
|
# savemail.c:1040|if (printheader && !putline(" ----- Transcript of session follows -----\n",
|
@@ -26,29 +28,30 @@ module Sisimai::Lhost
|
|
26
28
|
# @param [String] mbody Message body of a bounce email
|
27
29
|
# @return [Hash] Bounce data list and message/rfc822 part
|
28
30
|
# @return [Nil] it failed to parse or the arguments are missing
|
29
|
-
def
|
30
|
-
return nil unless mhead['subject'] =~ /(?:see transcript for details\z|\AWarning: )/
|
31
|
+
def inquire(mhead, mbody)
|
31
32
|
return nil if mhead['x-aol-ip']
|
33
|
+
match = nil
|
34
|
+
match ||= true if mhead['subject'].end_with?('see transcript for details')
|
35
|
+
match ||= true if mhead['subject'].start_with?('Warning: ')
|
36
|
+
return nil unless match
|
32
37
|
|
33
|
-
require 'sisimai/rfc1894'
|
34
38
|
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
35
39
|
permessage = {} # (Hash) Store values of each Per-Message field
|
36
|
-
|
37
40
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
38
|
-
|
39
|
-
bodyslices =
|
41
|
+
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
42
|
+
bodyslices = emailparts[0].split("\n")
|
40
43
|
readslices = ['']
|
41
44
|
readcursor = 0 # (Integer) Points the current cursor position
|
42
45
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
43
|
-
|
46
|
+
thecommand = '' # (String) SMTP Command name begin with the string '>>>'
|
44
47
|
esmtpreply = [] # (Array) Reply from remote server on SMTP session
|
45
48
|
sessionerr = false # (Boolean) Flag, "true" if it is SMTP session error
|
46
49
|
anotherset = {} # Another error information
|
47
50
|
v = nil
|
48
51
|
|
49
52
|
while e = bodyslices.shift do
|
50
|
-
# Read error messages and delivery status lines from the head of the email
|
51
|
-
#
|
53
|
+
# Read error messages and delivery status lines from the head of the email to the previous
|
54
|
+
# line of the beginning of the original message.
|
52
55
|
readslices << e # Save the current line for the next loop
|
53
56
|
|
54
57
|
if readcursor == 0
|
@@ -89,7 +92,7 @@ module Sisimai::Lhost
|
|
89
92
|
next unless fieldtable[o[0]]
|
90
93
|
v[fieldtable[o[0]]] = o[2]
|
91
94
|
|
92
|
-
next unless f
|
95
|
+
next unless f
|
93
96
|
permessage[fieldtable[o[0]]] = o[2]
|
94
97
|
end
|
95
98
|
else
|
@@ -105,13 +108,14 @@ module Sisimai::Lhost
|
|
105
108
|
# Received-From-MTA: DNS; x1x2x3x4.dhcp.example.ne.jp
|
106
109
|
# Arrival-Date: Wed, 29 Apr 2009 16:03:18 +0900
|
107
110
|
unless e.start_with?(' ')
|
108
|
-
if
|
111
|
+
if e.start_with?('>>> ')
|
109
112
|
# >>> DATA
|
110
|
-
|
113
|
+
thecommand = Sisimai::SMTP::Command.find(e)
|
111
114
|
|
112
|
-
elsif
|
115
|
+
elsif e.start_with?('<<< ')
|
113
116
|
# <<< Response
|
114
|
-
|
117
|
+
cv = e[4, e.size - 4]
|
118
|
+
esmtpreply << cv unless esmtpreply.index(cv)
|
115
119
|
else
|
116
120
|
# Detect SMTP session error or connection error
|
117
121
|
next if sessionerr
|
@@ -122,21 +126,23 @@ module Sisimai::Lhost
|
|
122
126
|
next
|
123
127
|
end
|
124
128
|
|
125
|
-
if
|
129
|
+
if e.start_with?('<') && Sisimai::String.aligned(e, ['@', '>.', ' '])
|
126
130
|
# <kijitora@example.co.jp>... Deferred: Name server: example.co.jp.: host name lookup failure
|
127
|
-
anotherset['recipient'] =
|
128
|
-
anotherset['diagnosis'] =
|
131
|
+
anotherset['recipient'] = Sisimai::Address.s3s4(e[0, e.index('>')])
|
132
|
+
anotherset['diagnosis'] = e[e.index(' ') + 1, e.size]
|
129
133
|
else
|
130
134
|
# ----- Transcript of session follows -----
|
131
135
|
# Message could not be delivered for too long
|
132
136
|
# Message will be deleted from queue
|
133
|
-
|
134
|
-
|
137
|
+
cr = Sisimai::SMTP::Reply.find(e) || ''
|
138
|
+
cs = Sisimai::SMTP::Status.find(e) || ''
|
139
|
+
|
140
|
+
if cr.size + cs.size > 7
|
135
141
|
# 550 5.1.2 <kijitora@example.org>... Message
|
136
142
|
#
|
137
143
|
# DBI connect('dbname=...')
|
138
144
|
# 554 5.3.0 unknown mailer error 255
|
139
|
-
anotherset['status']
|
145
|
+
anotherset['status'] = cs
|
140
146
|
anotherset['diagnosis'] ||= ''
|
141
147
|
anotherset['diagnosis'] << ' ' << e
|
142
148
|
|
@@ -151,8 +157,8 @@ module Sisimai::Lhost
|
|
151
157
|
else
|
152
158
|
# Continued line of the value of Diagnostic-Code field
|
153
159
|
next unless readslices[-2].start_with?('Diagnostic-Code:')
|
154
|
-
next unless
|
155
|
-
v['diagnosis'] << ' ' <<
|
160
|
+
next unless e.start_with?(' ')
|
161
|
+
v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
|
156
162
|
readslices[-1] = 'Diagnostic-Code: ' << e
|
157
163
|
end
|
158
164
|
end
|
@@ -161,47 +167,54 @@ module Sisimai::Lhost
|
|
161
167
|
|
162
168
|
dscontents.each do |e|
|
163
169
|
# Set default values if each value is empty.
|
164
|
-
e['
|
170
|
+
e['diagnosis'] ||= ''
|
171
|
+
e['lhost'] ||= permessage['rhost']
|
165
172
|
permessage.each_key { |a| e[a] ||= permessage[a] || '' }
|
166
173
|
|
167
|
-
e['command'] ||= commandtxt
|
168
|
-
if e['command'].empty?
|
169
|
-
e['command'] = 'EHLO' unless esmtpreply.empty?
|
170
|
-
end
|
171
|
-
|
172
174
|
if anotherset['diagnosis']
|
173
175
|
# Copy alternative error message
|
174
|
-
e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis']
|
175
|
-
e['diagnosis'] = anotherset['diagnosis']
|
176
|
-
e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis']
|
176
|
+
e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].start_with?(' ')
|
177
|
+
e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].=~ /\A\d+\z/
|
178
|
+
e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].empty?
|
177
179
|
end
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
180
|
+
|
181
|
+
while true
|
182
|
+
# Replace or append the error message in "diagnosis" with the ESMTP Reply Code when the
|
183
|
+
# following conditions have matched
|
184
|
+
break if esmtpreply.empty?
|
185
|
+
break if recipients != 1
|
186
|
+
|
187
|
+
e['diagnosis'] = sprintf("%s %s", esmtpreply.join(' '), e['diagnosis'])
|
188
|
+
break
|
182
189
|
end
|
183
|
-
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
184
190
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
e['status'] = anotherset['status']
|
190
|
-
end
|
191
|
+
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
192
|
+
e['command'] ||= thecommand || Sisimai::SMTP::Command.find(e['diagnosis']) || ''
|
193
|
+
if e['command'].empty?
|
194
|
+
e['command'] = 'EHLO' unless esmtpreply.empty?
|
191
195
|
end
|
192
196
|
|
193
|
-
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
197
|
+
while true
|
198
|
+
# Check alternative status code and override it
|
199
|
+
break unless anotherset.has_key?('status')
|
200
|
+
break unless anotherset['status'].size > 0
|
201
|
+
break if Sisimai::SMTP::Status.test(e['status'])
|
202
|
+
|
203
|
+
e['status'] = anotherset['status']
|
204
|
+
break
|
199
205
|
end
|
206
|
+
|
207
|
+
# @example.jp, no local part
|
208
|
+
# # Get email address from the value of Diagnostic-Code field
|
209
|
+
next unless e['recipient'].start_with?('@')
|
210
|
+
cv = Sisimai::Address.find(e['diagnosis'], true) || []
|
211
|
+
e['recipient'] = cv[0][:address] if cv.size > 0
|
200
212
|
end
|
201
213
|
|
202
|
-
return { 'ds' => dscontents, 'rfc822' =>
|
214
|
+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
203
215
|
end
|
204
216
|
def description; return 'V8Sendmail: /usr/sbin/sendmail'; end
|
205
217
|
end
|
206
218
|
end
|
207
219
|
end
|
220
|
+
|