sisimai 4.25.16 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +412 -393
- data/Developers.mk +5 -6
- data/Gemfile +1 -1
- data/Makefile +15 -15
- data/README-JA.md +140 -78
- data/README.md +290 -143
- 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 +23 -18
- 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 -326
- 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 +12 -12
- 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 -20
- 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/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
|
+
|