sisimai 4.14.2 → 4.15.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sisimai might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop.yml +19 -2
- data/.travis.yml +0 -1
- data/Changes +16 -0
- data/README.md +69 -17
- data/appveyor.yml +23 -0
- data/lib/sisimai.rb +9 -7
- data/lib/sisimai/address.rb +8 -2
- data/lib/sisimai/arf.rb +1 -6
- data/lib/sisimai/data.rb +35 -47
- data/lib/sisimai/data/json.rb +16 -12
- data/lib/sisimai/datetime.rb +1 -8
- data/lib/sisimai/mail/maildir.rb +0 -3
- data/lib/sisimai/mail/mbox.rb +1 -1
- data/lib/sisimai/mail/stdin.rb +2 -1
- data/lib/sisimai/message.rb +46 -31
- data/lib/sisimai/mime.rb +2 -2
- data/lib/sisimai/msp.rb +3 -3
- data/lib/sisimai/msp/de/einsundeins.rb +1 -1
- data/lib/sisimai/msp/de/gmx.rb +1 -1
- data/lib/sisimai/msp/jp/biglobe.rb +2 -2
- data/lib/sisimai/msp/jp/ezweb.rb +10 -10
- data/lib/sisimai/msp/jp/kddi.rb +1 -1
- data/lib/sisimai/msp/ru/yandex.rb +1 -1
- data/lib/sisimai/msp/uk/messagelabs.rb +1 -1
- data/lib/sisimai/msp/us/amazonses.rb +1 -1
- data/lib/sisimai/msp/us/amazonworkmail.rb +224 -0
- data/lib/sisimai/msp/us/aol.rb +1 -1
- data/lib/sisimai/msp/us/bigfoot.rb +1 -1
- data/lib/sisimai/msp/us/facebook.rb +1 -1
- data/lib/sisimai/msp/us/google.rb +1 -1
- data/lib/sisimai/msp/us/office365.rb +273 -0
- data/lib/sisimai/msp/us/outlook.rb +2 -3
- data/lib/sisimai/msp/us/receivingses.rb +1 -1
- data/lib/sisimai/msp/us/sendgrid.rb +2 -2
- data/lib/sisimai/msp/us/verizon.rb +7 -7
- data/lib/sisimai/msp/us/yahoo.rb +1 -1
- data/lib/sisimai/msp/us/zoho.rb +1 -1
- data/lib/sisimai/mta.rb +8 -9
- data/lib/sisimai/mta/activehunter.rb +1 -1
- data/lib/sisimai/mta/apachejames.rb +1 -1
- data/lib/sisimai/mta/courier.rb +1 -1
- data/lib/sisimai/mta/domino.rb +1 -1
- data/lib/sisimai/mta/exchange.rb +15 -15
- data/lib/sisimai/mta/imailserver.rb +1 -1
- data/lib/sisimai/mta/interscanmss.rb +1 -1
- data/lib/sisimai/mta/mailfoundry.rb +1 -1
- data/lib/sisimai/mta/mailmarshalsmtp.rb +1 -1
- data/lib/sisimai/mta/mcafee.rb +1 -1
- data/lib/sisimai/mta/messagingserver.rb +1 -1
- data/lib/sisimai/mta/mxlogic.rb +2 -2
- data/lib/sisimai/mta/notes.rb +1 -1
- data/lib/sisimai/mta/opensmtpd.rb +1 -1
- data/lib/sisimai/mta/postfix.rb +2 -2
- data/lib/sisimai/mta/qmail.rb +1 -1
- data/lib/sisimai/mta/sendmail.rb +2 -2
- data/lib/sisimai/mta/surfcontrol.rb +1 -1
- data/lib/sisimai/mta/v5sendmail.rb +1 -1
- data/lib/sisimai/mta/x1.rb +1 -1
- data/lib/sisimai/mta/x2.rb +1 -1
- data/lib/sisimai/mta/x3.rb +1 -1
- data/lib/sisimai/mta/x4.rb +1 -1
- data/lib/sisimai/mta/x5.rb +1 -1
- data/lib/sisimai/order.rb +4 -1
- data/lib/sisimai/reason.rb +15 -18
- data/lib/sisimai/reason/hostunknown.rb +5 -0
- data/lib/sisimai/reason/mesgtoobig.rb +5 -0
- data/lib/sisimai/reason/userunknown.rb +1 -1
- data/lib/sisimai/rfc3464.rb +3 -3
- data/lib/sisimai/rfc3834.rb +1 -1
- data/lib/sisimai/time.rb +1 -1
- data/lib/sisimai/version.rb +1 -1
- data/set-of-emails/maildir/bsd/us-amazonworkmail-01.eml +156 -0
- data/set-of-emails/maildir/bsd/us-amazonworkmail-02.eml +160 -0
- data/set-of-emails/maildir/bsd/us-amazonworkmail-03.eml +161 -0
- data/set-of-emails/maildir/bsd/us-amazonworkmail-04.eml +156 -0
- data/set-of-emails/maildir/bsd/us-amazonworkmail-05.eml +157 -0
- data/set-of-emails/maildir/bsd/us-office365-01.eml +102 -0
- data/sisimai.gemspec +7 -0
- metadata +26 -3
@@ -0,0 +1,224 @@
|
|
1
|
+
module Sisimai
|
2
|
+
module MSP::US
|
3
|
+
# Sisimai::MSP::US::AmazonWorkMail parses a bounce email which created by
|
4
|
+
# Amazon WorkMail. Methods in the module are called from only Sisimai::Message.
|
5
|
+
module AmazonWorkMail
|
6
|
+
# Imported from p5-Sisimail/lib/Sisimai/MSP/US/AmazonWorkMail.pm
|
7
|
+
class << self
|
8
|
+
require 'sisimai/msp'
|
9
|
+
require 'sisimai/rfc5322'
|
10
|
+
|
11
|
+
# https://aws.amazon.com/workmail/
|
12
|
+
Re0 = {
|
13
|
+
:'subject' => %r/Delivery[_ ]Status[_ ]Notification[_ ].+Failure/,
|
14
|
+
:'received' => %r/.+[.]smtp-out[.].+[.]amazonses[.]com\b/,
|
15
|
+
:'x-mailer' => %r/\AAmazon WorkMail\z/,
|
16
|
+
}
|
17
|
+
Re1 = {
|
18
|
+
:begin => %r/\ATechnical report:\z/,
|
19
|
+
:rfc822 => %r|\Acontent-type: message/rfc822\z|,
|
20
|
+
:endof => %r/\A__END_OF_EMAIL_MESSAGE__\z/,
|
21
|
+
}
|
22
|
+
Indicators = Sisimai::MSP.INDICATORS
|
23
|
+
LongFields = Sisimai::RFC5322.LONGFIELDS
|
24
|
+
RFC822Head = Sisimai::RFC5322.HEADERFIELDS
|
25
|
+
|
26
|
+
def description; return 'Amazon WorkMail: https://aws.amazon.com/workmail/'; end
|
27
|
+
def smtpagent; return 'US::AmazonWorkMail'; end
|
28
|
+
|
29
|
+
# X-Mailer: Amazon WorkMail
|
30
|
+
# X-Original-Mailer: Amazon WorkMail
|
31
|
+
# X-Ses-Outgoing: 2016.01.14-54.240.27.159
|
32
|
+
def headerlist; return ['X-SES-Outgoing', 'X-Original-Mailer']; end
|
33
|
+
def pattern; return Re0; end
|
34
|
+
|
35
|
+
# Parse bounce messages from Amazon WorkMail
|
36
|
+
# @param [Hash] mhead Message header of a bounce email
|
37
|
+
# @options mhead [String] from From header
|
38
|
+
# @options mhead [String] date Date header
|
39
|
+
# @options mhead [String] subject Subject header
|
40
|
+
# @options mhead [Array] received Received headers
|
41
|
+
# @options mhead [String] others Other required headers
|
42
|
+
# @param [String] mbody Message body of a bounce email
|
43
|
+
# @return [Hash, Nil] Bounce data list and message/rfc822
|
44
|
+
# part or nil if it failed to parse or
|
45
|
+
# the arguments are missing
|
46
|
+
def scan(mhead, mbody)
|
47
|
+
return nil unless mhead
|
48
|
+
return nil unless mbody
|
49
|
+
|
50
|
+
match = 0
|
51
|
+
xmail = mhead['x-original-mailer'] || mhead['x-mailer'] || ''
|
52
|
+
|
53
|
+
match += 1 if mhead['x-ses-outgoing']
|
54
|
+
unless xmail.empty?
|
55
|
+
# X-Mailer: Amazon WorkMail
|
56
|
+
# X-Original-Mailer: Amazon WorkMail
|
57
|
+
match += 1 if xmail =~ Re0[:'x-mailer']
|
58
|
+
end
|
59
|
+
return nil if match < 2
|
60
|
+
|
61
|
+
if mbody =~ /^Content-Transfer-Encoding: quoted-printable$/
|
62
|
+
# This is a multi-part message in MIME format. Your mail reader does not
|
63
|
+
# understand MIME message format.
|
64
|
+
# --=_gy7C4Gpes0RP4V5Bs9cK4o2Us2ZT57b-3OLnRN+4klS8dTmQ
|
65
|
+
# Content-Type: text/plain; charset=iso-8859-15
|
66
|
+
# Content-Transfer-Encoding: quoted-printable
|
67
|
+
require 'sisimai/mime'
|
68
|
+
mbody = mbody.sub(/\A.+?quoted-printable/ms, '')
|
69
|
+
mbody = Sisimai::MIME.qprintd(mbody)
|
70
|
+
end
|
71
|
+
|
72
|
+
dscontents = []; dscontents << Sisimai::MSP.DELIVERYSTATUS
|
73
|
+
hasdivided = mbody.split("\n")
|
74
|
+
rfc822next = { 'from' => false, 'to' => false, 'subject' => false }
|
75
|
+
rfc822part = '' # (String) message/rfc822-headers part
|
76
|
+
previousfn = '' # (String) Previous field name
|
77
|
+
readcursor = 0 # (Integer) Points the current cursor position
|
78
|
+
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
79
|
+
connvalues = 0 # (Integer) Flag, 1 if all the value of $connheader have been set
|
80
|
+
connheader = {
|
81
|
+
'lhost' => '', # The value of Reporting-MTA header
|
82
|
+
}
|
83
|
+
v = nil
|
84
|
+
|
85
|
+
hasdivided.each do |e|
|
86
|
+
if readcursor == 0
|
87
|
+
# Beginning of the bounce message or delivery status part
|
88
|
+
if e =~ Re1[:begin]
|
89
|
+
readcursor |= Indicators[:'deliverystatus']
|
90
|
+
next
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
if readcursor & Indicators[:'message-rfc822'] == 0
|
95
|
+
# Beginning of the original message part
|
96
|
+
if e =~ Re1[:rfc822]
|
97
|
+
readcursor |= Indicators[:'message-rfc822']
|
98
|
+
next
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
if readcursor & Indicators[:'message-rfc822'] > 0
|
103
|
+
# After "message/rfc822"
|
104
|
+
if cv = e.match(/\A([-0-9A-Za-z]+?)[:][ ]*.+\z/)
|
105
|
+
# Get required headers only
|
106
|
+
lhs = cv[1].downcase
|
107
|
+
previousfn = ''
|
108
|
+
next unless RFC822Head.key?(lhs)
|
109
|
+
|
110
|
+
previousfn = lhs
|
111
|
+
rfc822part += e + "\n"
|
112
|
+
|
113
|
+
elsif e =~ /\A[ \t]+/
|
114
|
+
# Continued line from the previous line
|
115
|
+
next if rfc822next[previousfn]
|
116
|
+
rfc822part += e + "\n" if LongFields.key?(previousfn)
|
117
|
+
|
118
|
+
else
|
119
|
+
# Check the end of headers in rfc822 part
|
120
|
+
next unless LongFields.key?(previousfn)
|
121
|
+
next unless e.empty?
|
122
|
+
rfc822next[previousfn] = true
|
123
|
+
end
|
124
|
+
|
125
|
+
else
|
126
|
+
# Before "message/rfc822"
|
127
|
+
next if readcursor & Indicators[:'deliverystatus'] == 0
|
128
|
+
next if e.empty?
|
129
|
+
|
130
|
+
if connvalues == connheader.keys.size
|
131
|
+
# Action: failed
|
132
|
+
# Final-Recipient: rfc822; kijitora@libsisimai.org
|
133
|
+
# Diagnostic-Code: smtp; 554 4.4.7 Message expired: unable to deliver in 840 minutes.<421 4.4.2 Connection timed out>
|
134
|
+
# Status: 4.4.7
|
135
|
+
v = dscontents[-1]
|
136
|
+
|
137
|
+
if cv = e.match(/\A[Ff]inal-[Rr]ecipient:[ ]*(?:RFC|rfc)822;[ ]*([^ ]+)\z/)
|
138
|
+
# Final-Recipient: RFC822; kijitora@example.jp
|
139
|
+
if v['recipient']
|
140
|
+
# There are multiple recipient addresses in the message body.
|
141
|
+
dscontents << Sisimai::MSP.DELIVERYSTATUS
|
142
|
+
v = dscontents[-1]
|
143
|
+
end
|
144
|
+
v['recipient'] = cv[1]
|
145
|
+
recipients += 1
|
146
|
+
|
147
|
+
elsif cv = e.match(/\A[Aa]ction:[ ]*(.+)\z/)
|
148
|
+
# Action: failed
|
149
|
+
v['action'] = cv[1].downcase
|
150
|
+
|
151
|
+
elsif cv = e.match(/\A[Ss]tatus:[ ]*(\d[.]\d+[.]\d+)/)
|
152
|
+
# Status: 5.1.1
|
153
|
+
v['status'] = cv[1]
|
154
|
+
|
155
|
+
else
|
156
|
+
if cv = e.match(/\A[Dd]iagnostic-[Cc]ode:[ ]*(.+?);[ ]*(.+)\z/)
|
157
|
+
# Diagnostic-Code: SMTP; 550 5.1.1 <kijitora@example.jp>... User Unknown
|
158
|
+
v['spec'] = cv[1].upcase
|
159
|
+
v['diagnosis'] = cv[2]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
else
|
163
|
+
# Technical report:
|
164
|
+
#
|
165
|
+
# Reporting-MTA: dsn; a27-85.smtp-out.us-west-2.amazonses.com
|
166
|
+
#
|
167
|
+
if cv = e.match(/\A[Rr]eporting-MTA:[ ]*[DNSdns]+;[ ]*(.+)\z/)
|
168
|
+
# Reporting-MTA: dns; mx.example.jp
|
169
|
+
next if connheader['lhost'].size > 0
|
170
|
+
connheader['lhost'] = cv[1].downcase
|
171
|
+
connvalues += 1
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# <!DOCTYPE HTML><html>
|
176
|
+
# <head>
|
177
|
+
# <meta name="Generator" content="Amazon WorkMail v3.0-2023.77">
|
178
|
+
# <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
179
|
+
break if e =~ /\A[<]!DOCTYPE HTML[>][<]html[>]\z/
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
return nil if recipients == 0
|
184
|
+
require 'sisimai/string'
|
185
|
+
require 'sisimai/smtp/status'
|
186
|
+
|
187
|
+
dscontents.map do |e|
|
188
|
+
# Set default values if each value is empty.
|
189
|
+
connheader.each_key { |a| e[a] ||= connheader[a] || '' }
|
190
|
+
|
191
|
+
if mhead['received'].size > 0
|
192
|
+
# Get localhost and remote host name from Received header.
|
193
|
+
r0 = mhead['received']
|
194
|
+
%w|lhost rhost|.each { |a| e[a] ||= '' }
|
195
|
+
e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
|
196
|
+
e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
|
197
|
+
end
|
198
|
+
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
199
|
+
|
200
|
+
if e['status'] =~ /\A[45][.][01][.]0\z/
|
201
|
+
# Get other D.S.N. value from the error message
|
202
|
+
errormessage = e['diagnosis']
|
203
|
+
|
204
|
+
if cv = e['diagnosis'].match(/["'](\d[.]\d[.]\d.+)['"]/)
|
205
|
+
# 5.1.0 - Unknown address error 550-'5.7.1 ...
|
206
|
+
errormessage = cv[1]
|
207
|
+
end
|
208
|
+
pseudostatus = Sisimai::SMTP::Status.find(errormessage)
|
209
|
+
e['status'] = pseudostatus if pseudostatus.size > 0
|
210
|
+
end
|
211
|
+
|
212
|
+
e['reason'] ||= Sisimai::SMTP::Status.name(e['status'])
|
213
|
+
e['spec'] ||= 'SMTP'
|
214
|
+
e['agent'] = Sisimai::MSP::US::AmazonWorkMail.smtpagent
|
215
|
+
end
|
216
|
+
|
217
|
+
return { 'ds' => dscontents, 'rfc822' => rfc822part }
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
data/lib/sisimai/msp/us/aol.rb
CHANGED
@@ -201,7 +201,7 @@ module Sisimai
|
|
201
201
|
if mhead['received'].size > 0
|
202
202
|
# Get localhost and remote host name from Received header.
|
203
203
|
r0 = mhead['received']
|
204
|
-
|
204
|
+
%w|lhost rhost|.each { |a| e[a] ||= '' }
|
205
205
|
e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
|
206
206
|
e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
|
207
207
|
end
|
@@ -212,7 +212,7 @@ module Sisimai
|
|
212
212
|
if mhead['received'].size > 0
|
213
213
|
# Get localhost and remote host name from Received header.
|
214
214
|
r0 = mhead['received']
|
215
|
-
|
215
|
+
%w|lhost rhost|.each { |a| e[a] ||= '' }
|
216
216
|
e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
|
217
217
|
e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
|
218
218
|
end
|
@@ -245,7 +245,7 @@ module Sisimai
|
|
245
245
|
if mhead['received'].size > 0
|
246
246
|
# Get localhost and remote host name from Received header.
|
247
247
|
r0 = mhead['received']
|
248
|
-
|
248
|
+
%w|lhost rhost|.each { |a| e[a] ||= '' }
|
249
249
|
e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
|
250
250
|
e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
|
251
251
|
end
|
@@ -311,7 +311,7 @@ module Sisimai
|
|
311
311
|
if mhead['received'].size > 0
|
312
312
|
# Get localhost and remote host name from Received header.
|
313
313
|
r0 = mhead['received']
|
314
|
-
|
314
|
+
%w|lhost rhost|.each { |a| e[a] ||= '' }
|
315
315
|
e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
|
316
316
|
e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
|
317
317
|
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
module Sisimai
|
2
|
+
module MSP::US
|
3
|
+
# Sisimai::MSP::US::Office365 parses a bounce email which created by Microsoft
|
4
|
+
# Office 365. Methods in the module are called from only Sisimai::Message.
|
5
|
+
module Office365
|
6
|
+
# Imported from p5-Sisimail/lib/Sisimai/MSP/US/Office365.pm
|
7
|
+
class << self
|
8
|
+
require 'sisimai/msp'
|
9
|
+
require 'sisimai/rfc5322'
|
10
|
+
|
11
|
+
Re0 = {
|
12
|
+
:subject => %r/Undeliverable:/,
|
13
|
+
:received => %r/.+[.](?:outbound[.]protection|prod)[.]outlook[.]com\b/,
|
14
|
+
}
|
15
|
+
Re1 = {
|
16
|
+
:begin => %r{\A(?:
|
17
|
+
Delivery[ ]has[ ]failed[ ]to[ ]these[ ]recipients[ ]or[ ]groups:
|
18
|
+
|.+[ ]rejected[ ]your[ ]message[ ]to[ ]the[ ]following[ ]e[-]?mail[ ]addresses:
|
19
|
+
)
|
20
|
+
}x,
|
21
|
+
:error => %r/\ADiagnostic information for administrators:\z/,
|
22
|
+
:eoerr => %r/\AOriginal message headers:\z/,
|
23
|
+
:rfc822 => %r|\AContent-Type: message/rfc822\z|,
|
24
|
+
:endof => %r/\A__END_OF_EMAIL_MESSAGE__\z/,
|
25
|
+
}
|
26
|
+
CodeTable = {
|
27
|
+
'4.4.7' => 'expired',
|
28
|
+
'5.1.0' => 'rejected',
|
29
|
+
'5.1.1' => 'userunknown',
|
30
|
+
'5.1.10' => 'filtered',
|
31
|
+
'5.4.1' => 'networkerror',
|
32
|
+
'5.4.14' => 'networkerror',
|
33
|
+
'5.7.1' => 'rejected',
|
34
|
+
'5.7.133' => 'rejected',
|
35
|
+
}
|
36
|
+
Indicators = Sisimai::MSP.INDICATORS
|
37
|
+
LongFields = Sisimai::RFC5322.LONGFIELDS
|
38
|
+
RFC822Head = Sisimai::RFC5322.HEADERFIELDS
|
39
|
+
|
40
|
+
def description; return 'Microsoft Office 365: http://office.microsoft.com/'; end
|
41
|
+
def smtpagent; return 'US::Office365'; end
|
42
|
+
|
43
|
+
def headerlist
|
44
|
+
# X-MS-Exchange-Message-Is-Ndr:
|
45
|
+
# X-Microsoft-Antispam-PRVS: <....@...outlook.com>
|
46
|
+
# X-Exchange-Antispam-Report-Test: UriScan:;
|
47
|
+
# X-Exchange-Antispam-Report-CFA-Test:
|
48
|
+
# X-MS-Exchange-CrossTenant-OriginalArrivalTime: 29 Apr 2015 23:34:45.6789 (JST)
|
49
|
+
# X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted
|
50
|
+
# X-MS-Exchange-Transport-CrossTenantHeadersStamped: ...
|
51
|
+
return [
|
52
|
+
'X-MS-Exchange-Message-Is-Ndr',
|
53
|
+
'X-Microsoft-Antispam-PRVS',
|
54
|
+
'X-Exchange-Antispam-Report-Test',
|
55
|
+
'X-Exchange-Antispam-Report-CFA-Test',
|
56
|
+
'X-MS-Exchange-CrossTenant-OriginalArrivalTime',
|
57
|
+
'X-MS-Exchange-CrossTenant-FromEntityHeader',
|
58
|
+
'X-MS-Exchange-Transport-CrossTenantHeadersStamped',
|
59
|
+
]
|
60
|
+
end
|
61
|
+
def pattern; return Re0; end
|
62
|
+
|
63
|
+
# Parse bounce messages from Microsoft Office 365
|
64
|
+
# @param [Hash] mhead Message header of a bounce email
|
65
|
+
# @options mhead [String] from From header
|
66
|
+
# @options mhead [String] date Date header
|
67
|
+
# @options mhead [String] subject Subject header
|
68
|
+
# @options mhead [Array] received Received headers
|
69
|
+
# @options mhead [String] others Other required headers
|
70
|
+
# @param [String] mbody Message body of a bounce email
|
71
|
+
# @return [Hash, Nil] Bounce data list and message/rfc822
|
72
|
+
# part or nil if it failed to parse or
|
73
|
+
# the arguments are missing
|
74
|
+
def scan(mhead, mbody)
|
75
|
+
return nil unless mhead
|
76
|
+
return nil unless mbody
|
77
|
+
|
78
|
+
match = 0
|
79
|
+
match += 1 if mhead['subject'] =~ Re0[:subject]
|
80
|
+
match += 1 if mhead['x-ms-exchange-message-is-ndr']
|
81
|
+
match += 1 if mhead['x-microsoft-antispam-prvs']
|
82
|
+
match += 1 if mhead['x-exchange-antispam-report-test']
|
83
|
+
match += 1 if mhead['x-exchange-antispam-report-cfa-test']
|
84
|
+
match += 1 if mhead['x-ms-exchange-crosstenant-originalarrivaltime']
|
85
|
+
match += 1 if mhead['x-ms-exchange-crosstenant-fromentityheader']
|
86
|
+
match += 1 if mhead['x-ms-exchange-transport-crosstenantheadersstamped']
|
87
|
+
match += 1 if mhead['received'].find { |a| a =~ Re0[:received] }
|
88
|
+
return nil if match < 2
|
89
|
+
|
90
|
+
if mbody =~ /^Content-Transfer-Encoding: quoted-printable$/
|
91
|
+
# This is a multi-part message in MIME format. Your mail reader does not
|
92
|
+
# understand MIME message format.
|
93
|
+
# --=_gy7C4Gpes0RP4V5Bs9cK4o2Us2ZT57b-3OLnRN+4klS8dTmQ
|
94
|
+
# Content-Type: text/plain; charset=iso-8859-15
|
95
|
+
# Content-Transfer-Encoding: quoted-printable
|
96
|
+
require 'sisimai/mime'
|
97
|
+
mbody = mbody.sub(/\A.+?quoted-printable/ms, '')
|
98
|
+
mbody = Sisimai::MIME.qprintd(mbody)
|
99
|
+
end
|
100
|
+
|
101
|
+
dscontents = []; dscontents << Sisimai::MSP.DELIVERYSTATUS
|
102
|
+
hasdivided = mbody.split("\n")
|
103
|
+
rfc822next = { 'from' => false, 'to' => false, 'subject' => false }
|
104
|
+
rfc822part = '' # (String) message/rfc822-headers part
|
105
|
+
previousfn = '' # (String) Previous field name
|
106
|
+
readcursor = 0 # (Integer) Points the current cursor position
|
107
|
+
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
108
|
+
connheader = {}
|
109
|
+
endoferror = false # (Boolean) Flag for the end of error messages
|
110
|
+
htmlbegins = false # (Boolean) Flag for HTML part
|
111
|
+
v = nil
|
112
|
+
|
113
|
+
hasdivided.each do |e|
|
114
|
+
if readcursor == 0
|
115
|
+
# Beginning of the bounce message or delivery status part
|
116
|
+
if e =~ Re1[:begin]
|
117
|
+
readcursor |= Indicators[:'deliverystatus']
|
118
|
+
next
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
if readcursor & Indicators[:'message-rfc822'] == 0
|
123
|
+
# Beginning of the original message part
|
124
|
+
if e =~ Re1[:rfc822]
|
125
|
+
readcursor |= Indicators[:'message-rfc822']
|
126
|
+
next
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
if readcursor & Indicators[:'message-rfc822'] > 0
|
131
|
+
# After "message/rfc822"
|
132
|
+
if cv = e.match(/\A([-0-9A-Za-z]+?)[:][ ]*.+\z/)
|
133
|
+
# Get required headers only
|
134
|
+
lhs = cv[1].downcase
|
135
|
+
previousfn = ''
|
136
|
+
next unless RFC822Head.key?(lhs)
|
137
|
+
|
138
|
+
previousfn = lhs
|
139
|
+
rfc822part += e + "\n"
|
140
|
+
|
141
|
+
elsif e =~ /\A[ \t]+/
|
142
|
+
# Continued line from the previous line
|
143
|
+
next if rfc822next[previousfn]
|
144
|
+
rfc822part += e + "\n" if LongFields.key?(previousfn)
|
145
|
+
|
146
|
+
else
|
147
|
+
# Check the end of headers in rfc822 part
|
148
|
+
next unless LongFields.key?(previousfn)
|
149
|
+
next unless e.empty?
|
150
|
+
rfc822next[previousfn] = true
|
151
|
+
end
|
152
|
+
|
153
|
+
else
|
154
|
+
# Before "message/rfc822"
|
155
|
+
next if readcursor & Indicators[:'deliverystatus'] == 0
|
156
|
+
next if e.empty?
|
157
|
+
|
158
|
+
# kijitora@example.com<mailto:kijitora@example.com>
|
159
|
+
# The email address wasn=92t found at the destination domain. It might be mis=
|
160
|
+
# spelled or it might not exist any longer. Try retyping the address and rese=
|
161
|
+
# nding the message.
|
162
|
+
v = dscontents[-1]
|
163
|
+
|
164
|
+
if cv = e.match(/\A.+[@].+[<]mailto:(.+[@].+)[>]\z/)
|
165
|
+
# kijitora@example.com<mailto:kijitora@example.com>
|
166
|
+
if v['recipient']
|
167
|
+
# There are multiple recipient addresses in the message body.
|
168
|
+
dscontents << Sisimai::MSP.DELIVERYSTATUS
|
169
|
+
v = dscontents[-1]
|
170
|
+
end
|
171
|
+
v['recipient'] = cv[1]
|
172
|
+
recipients += 1
|
173
|
+
|
174
|
+
elsif cv = e.match(/\AGenerating server: (.+)\z/)
|
175
|
+
# Generating server: FFFFFFFFFFFF.e0.prod.outlook.com
|
176
|
+
connheader['lhost'] = cv[1].downcase
|
177
|
+
|
178
|
+
else
|
179
|
+
if endoferror
|
180
|
+
# After "Original message headers:"
|
181
|
+
if htmlbegins
|
182
|
+
# <html> .. </html>
|
183
|
+
htmlbegins = false if e =~ %r|\A[<]/html[>]|
|
184
|
+
next
|
185
|
+
end
|
186
|
+
|
187
|
+
if cv = e.match(/\A[Aa]ction:[ ]*(.+)\z/)
|
188
|
+
# Action: failed
|
189
|
+
v['action'] = cv[1].downcase
|
190
|
+
|
191
|
+
elsif cv = e.match(/\A[Ss]tatus:[ ]*(\d[.]\d+[.]\d+)/)
|
192
|
+
# Status:5.2.0
|
193
|
+
v['status'] = cv[1]
|
194
|
+
|
195
|
+
elsif cv = e.match(/\A[Rr]eporting-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
|
196
|
+
# Reporting-MTA: dns;BLU004-OMC3S13.hotmail.example.com
|
197
|
+
connheader['lhost'] = cv[1].downcase
|
198
|
+
|
199
|
+
elsif cv = e.match(/\A[Rr]eceived-[Ff]rom-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
|
200
|
+
# Reporting-MTA: dns;BLU004-OMC3S13.hotmail.example.com
|
201
|
+
connheader['rhost'] = cv[1].downcase
|
202
|
+
|
203
|
+
elsif cv = e.match(/\A[Aa]rrival-[Dd]ate:[ ]*(.+)\z/)
|
204
|
+
# Arrival-Date: Wed, 29 Apr 2009 16:03:18 +0900
|
205
|
+
next if connheader['date']
|
206
|
+
connheader['date'] = cv[1]
|
207
|
+
|
208
|
+
else
|
209
|
+
htmlbegins = true if e =~ /\A[<]html[>]/
|
210
|
+
end
|
211
|
+
|
212
|
+
else
|
213
|
+
if e =~ Re1[:error]
|
214
|
+
# Diagnostic information for administrators:
|
215
|
+
v['diagnosis'] = e
|
216
|
+
else
|
217
|
+
# kijitora@example.com
|
218
|
+
# Remote Server returned '550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipien=
|
219
|
+
# t not found by SMTP address lookup'
|
220
|
+
next unless v['diagnosis']
|
221
|
+
if e =~ Re1[:eoerr]
|
222
|
+
# Original message headers:
|
223
|
+
endoferror = true
|
224
|
+
next
|
225
|
+
end
|
226
|
+
v['diagnosis'] += ' ' + e
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
return nil if recipients == 0
|
235
|
+
require 'sisimai/string'
|
236
|
+
require 'sisimai/smtp/status'
|
237
|
+
|
238
|
+
dscontents.map do |e|
|
239
|
+
# Set default values if each value is empty.
|
240
|
+
connheader.each_key { |a| e[a] ||= connheader[a] || '' }
|
241
|
+
|
242
|
+
if mhead['received'].size > 0
|
243
|
+
# Get localhost and remote host name from Received header.
|
244
|
+
r0 = mhead['received']
|
245
|
+
%w|lhost rhost|.each { |a| e[a] ||= '' }
|
246
|
+
e['lhost'] = Sisimai::RFC5322.received(r0[0]).shift if e['lhost'].empty?
|
247
|
+
e['rhost'] = Sisimai::RFC5322.received(r0[-1]).pop if e['rhost'].empty?
|
248
|
+
end
|
249
|
+
|
250
|
+
e['spec'] ||= 'SMTP'
|
251
|
+
e['status'] ||= ''
|
252
|
+
e['agent'] = Sisimai::MSP::US::Office365.smtpagent
|
253
|
+
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
|
254
|
+
|
255
|
+
if e['status'].empty? || e['status'] =~ /\A\d[.]0[.]0\z/
|
256
|
+
# There is no value of Status header or the value is 5.0.0, 4.0.0
|
257
|
+
pseudostatus = Sisimai::SMTP::Status.find(e['diagnosis'])
|
258
|
+
e['status'] = pseudostatus if pseudostatus.size > 0
|
259
|
+
end
|
260
|
+
|
261
|
+
if e['status']
|
262
|
+
e['reason'] = CodeTable[e['status']] || ''
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
return { 'ds' => dscontents, 'rfc822' => rfc822part }
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|