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
@@ -1,13 +1,12 @@
|
|
1
1
|
module Sisimai::Lhost
|
2
|
-
# Sisimai::Lhost::OpenSMTPD parses a bounce email which created by
|
3
|
-
#
|
2
|
+
# Sisimai::Lhost::OpenSMTPD parses a bounce email which created by OpenSMTPD. Methods in the module
|
3
|
+
# are called from only Sisimai::Message.
|
4
4
|
module OpenSMTPD
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/Lhost/OpenSMTPD.pm
|
7
6
|
require 'sisimai/lhost'
|
8
7
|
|
9
8
|
Indicators = Sisimai::Lhost.INDICATORS
|
10
|
-
|
9
|
+
Boundaries = [' Below is a copy of the original message:'].freeze
|
11
10
|
StartingOf = {
|
12
11
|
# http://www.openbsd.org/cgi-bin/man.cgi?query=smtpd&sektion=8
|
13
12
|
# opensmtpd-5.4.2p1/smtpd/
|
@@ -70,21 +69,21 @@ module Sisimai::Lhost
|
|
70
69
|
# @param [String] mbody Message body of a bounce email
|
71
70
|
# @return [Hash] Bounce data list and message/rfc822 part
|
72
71
|
# @return [Nil] it failed to parse or the arguments are missing
|
73
|
-
def
|
72
|
+
def inquire(mhead, mbody)
|
74
73
|
return nil unless mhead['subject'].start_with?('Delivery status notification')
|
75
74
|
return nil unless mhead['from'].start_with?('Mailer Daemon <')
|
76
75
|
return nil unless mhead['received'].any? { |a| a.include?(' (OpenSMTPD) with ') }
|
77
76
|
|
78
77
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
79
|
-
|
80
|
-
bodyslices =
|
78
|
+
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
79
|
+
bodyslices = emailparts[0].split("\n")
|
81
80
|
readcursor = 0 # (Integer) Points the current cursor position
|
82
81
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
83
82
|
v = nil
|
84
83
|
|
85
84
|
while e = bodyslices.shift do
|
86
|
-
# Read error messages and delivery status lines from the head of the email
|
87
|
-
#
|
85
|
+
# Read error messages and delivery status lines from the head of the email to the previous
|
86
|
+
# line of the beginning of the original message.
|
88
87
|
if readcursor == 0
|
89
88
|
# Beginning of the bounce message or delivery status part
|
90
89
|
readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
|
@@ -105,15 +104,15 @@ module Sisimai::Lhost
|
|
105
104
|
# Below is a copy of the original message:
|
106
105
|
v = dscontents[-1]
|
107
106
|
|
108
|
-
if
|
107
|
+
if e.include?('@') && Sisimai::String.aligned(e, ['@', ' '])
|
109
108
|
# kijitora@example.jp: 550 5.2.2 <kijitora@example>... Mailbox Full
|
110
109
|
if v['recipient']
|
111
110
|
# There are multiple recipient addresses in the message body.
|
112
111
|
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
113
112
|
v = dscontents[-1]
|
114
113
|
end
|
115
|
-
v['recipient'] =
|
116
|
-
v['diagnosis'] =
|
114
|
+
v['recipient'] = e[0, e.index(':')]
|
115
|
+
v['diagnosis'] = e[e.index(':') + 1, e.size]
|
117
116
|
recipients += 1
|
118
117
|
end
|
119
118
|
end
|
@@ -128,7 +127,7 @@ module Sisimai::Lhost
|
|
128
127
|
break
|
129
128
|
end
|
130
129
|
end
|
131
|
-
return { 'ds' => dscontents, 'rfc822' =>
|
130
|
+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
132
131
|
end
|
133
132
|
def description; return 'OpenSMTPD'; end
|
134
133
|
end
|
@@ -1,14 +1,12 @@
|
|
1
1
|
module Sisimai::Lhost
|
2
|
-
# Sisimai::Lhost::Outlook parses a bounce email which created by
|
3
|
-
#
|
4
|
-
# Methods in the module are called from only Sisimai::Message.
|
2
|
+
# Sisimai::Lhost::Outlook parses a bounce email which created by Microsoft Outlook.com. Methods in
|
3
|
+
# the module are called from only Sisimai::Message.
|
5
4
|
module Outlook
|
6
5
|
class << self
|
7
|
-
# Imported from p5-Sisimail/lib/Sisimai/Lhost/US/Outlook.pm
|
8
6
|
require 'sisimai/lhost'
|
9
7
|
|
10
8
|
Indicators = Sisimai::Lhost.INDICATORS
|
11
|
-
|
9
|
+
Boundaries = ['Content-Type: message/rfc822'].freeze
|
12
10
|
StartingOf = { message: ['This is an automatically generated Delivery Status Notification'] }.freeze
|
13
11
|
MessagesOf = {
|
14
12
|
'hostunknown' => ['The mail could not be delivered to the recipient because the domain is not reachable'],
|
@@ -20,7 +18,7 @@ module Sisimai::Lhost
|
|
20
18
|
# @param [String] mbody Message body of a bounce email
|
21
19
|
# @return [Hash] Bounce data list and message/rfc822 part
|
22
20
|
# @return [Nil] it failed to parse or the arguments are missing
|
23
|
-
def
|
21
|
+
def inquire(mhead, mbody)
|
24
22
|
# X-Message-Delivery: Vj0xLjE7RD0wO0dEPTA7U0NMPTk7bD0xO3VzPTE=
|
25
23
|
# X-Message-Info: AuEzbeVr9u5fkDpn2vR5iCu5wb6HBeY4iruBjnutBzpStnUabbM...
|
26
24
|
match = 0
|
@@ -30,21 +28,20 @@ module Sisimai::Lhost
|
|
30
28
|
match += 1 if mhead['received'].any? { |a| a.include?('.hotmail.com') }
|
31
29
|
return nil if match < 2
|
32
30
|
|
33
|
-
require 'sisimai/rfc1894'
|
34
31
|
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
35
32
|
permessage = {} # (Hash) Store values of each Per-Message field
|
36
33
|
|
37
34
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
38
|
-
|
39
|
-
bodyslices =
|
35
|
+
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
36
|
+
bodyslices = emailparts[0].split("\n")
|
40
37
|
readslices = ['']
|
41
38
|
readcursor = 0 # (Integer) Points the current cursor position
|
42
39
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
43
40
|
v = nil
|
44
41
|
|
45
42
|
while e = bodyslices.shift do
|
46
|
-
# Read error messages and delivery status lines from the head of the email
|
47
|
-
#
|
43
|
+
# Read error messages and delivery status lines from the head of the email to the previous
|
44
|
+
# line of the beginning of the original message.
|
48
45
|
readslices << e # Save the current line for the next loop
|
49
46
|
|
50
47
|
if readcursor == 0
|
@@ -85,14 +82,14 @@ module Sisimai::Lhost
|
|
85
82
|
next unless fieldtable[o[0]]
|
86
83
|
v[fieldtable[o[0]]] = o[2]
|
87
84
|
|
88
|
-
next unless f
|
85
|
+
next unless f
|
89
86
|
permessage[fieldtable[o[0]]] = o[2]
|
90
87
|
end
|
91
88
|
else
|
92
89
|
# Continued line of the value of Diagnostic-Code field
|
93
90
|
next unless readslices[-2].start_with?('Diagnostic-Code:')
|
94
|
-
next unless
|
95
|
-
v['diagnosis'] << ' ' <<
|
91
|
+
next unless e.start_with?(' ')
|
92
|
+
v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
|
96
93
|
readslices[-1] = 'Diagnostic-Code: ' << e
|
97
94
|
end
|
98
95
|
end
|
@@ -124,7 +121,7 @@ module Sisimai::Lhost
|
|
124
121
|
end
|
125
122
|
end
|
126
123
|
|
127
|
-
return { 'ds' => dscontents, 'rfc822' =>
|
124
|
+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
128
125
|
end
|
129
126
|
def description; return 'Microsoft Outlook.com: https://www.outlook.com/'; end
|
130
127
|
end
|
@@ -1,34 +1,23 @@
|
|
1
1
|
module Sisimai::Lhost
|
2
|
-
# Sisimai::Lhost::Postfix parses a bounce email which created by
|
3
|
-
#
|
2
|
+
# Sisimai::Lhost::Postfix parses a bounce email which created by Postfix. Methods in the module are
|
3
|
+
# called from only Sisimai::Message.
|
4
4
|
module Postfix
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/Lhost/Postfix.pm
|
7
6
|
require 'sisimai/lhost'
|
7
|
+
require 'sisimai/smtp/command'
|
8
8
|
|
9
9
|
# Postfix manual - bounce(5) - http://www.postfix.org/bounce.5.html
|
10
10
|
Indicators = Sisimai::Lhost.INDICATORS
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|\w+[ \t]program\z # The <custmized-name> program
|
22
|
-
)
|
23
|
-
|This[ ]is[ ]the[ ](?:
|
24
|
-
Postfix[ ]program # This is the Postfix program
|
25
|
-
|\w+[ ]Postfix[ ]program # This is the <name> Postfix program
|
26
|
-
|\w+[ ]program # This is the <customized-name> Postfix program
|
27
|
-
|mail[ ]system[ ]at[ ]host # This is the mail system at host <hostname>.
|
28
|
-
)
|
29
|
-
)
|
30
|
-
}x,
|
31
|
-
# :from => %r/ [(]Mail Delivery System[)]\z/,
|
11
|
+
Boundaries = ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'].freeze
|
12
|
+
StartingOf = {
|
13
|
+
# Postfix manual - bounce(5) - http://www.postfix.org/bounce.5.html
|
14
|
+
message: [
|
15
|
+
['The ', 'Postfix '], # The Postfix program, The Postfix on <os> program
|
16
|
+
['The ', 'mail system'], # The mail system
|
17
|
+
['The ', 'program'], # The <name> pogram
|
18
|
+
['This is the', 'Postfix'], # This is the Postfix program
|
19
|
+
['This is the', 'mail system'], # This is the mail system at host <hostname>
|
20
|
+
],
|
32
21
|
}.freeze
|
33
22
|
|
34
23
|
# Parse bounce messages from Postfix
|
@@ -36,109 +25,160 @@ module Sisimai::Lhost
|
|
36
25
|
# @param [String] mbody Message body of a bounce email
|
37
26
|
# @return [Hash] Bounce data list and message/rfc822 part
|
38
27
|
# @return [Nil] it failed to parse or the arguments are missing
|
39
|
-
def
|
40
|
-
|
28
|
+
def inquire(mhead, mbody)
|
29
|
+
match = nil
|
30
|
+
sessx = nil
|
31
|
+
|
32
|
+
if mhead['subject'].include?('SMTP server: errors from ')
|
33
|
+
# src/smtpd/smtpd_chat.c:|337: post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s",
|
34
|
+
# src/smtpd/smtpd_chat.c:|338: var_mail_name, state->namaddr);
|
35
|
+
match = true
|
36
|
+
sessx = true
|
37
|
+
else
|
38
|
+
# Subject: Undelivered Mail Returned to Sender
|
39
|
+
match = true if mhead['subject'] == 'Undelivered Mail Returned to Sender'
|
40
|
+
end
|
41
|
+
return nil unless match
|
41
42
|
return nil if mhead['x-aol-ip']
|
42
43
|
|
43
|
-
require 'sisimai/rfc1894'
|
44
|
-
require 'sisimai/address'
|
45
|
-
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
46
44
|
permessage = {} # (Hash) Store values of each Per-Message field
|
47
|
-
|
48
45
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
49
|
-
|
50
|
-
bodyslices =
|
46
|
+
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
47
|
+
bodyslices = emailparts[0].split("\n")
|
51
48
|
readslices = ['']
|
52
|
-
readcursor = 0 # (Integer) Points the current cursor position
|
53
49
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
54
50
|
nomessages = false # (Boolean) Delivery report unavailable
|
55
51
|
commandset = [] # (Array) ``in reply to * command'' list
|
56
52
|
anotherset = {} # Another error information
|
57
53
|
v = nil
|
58
54
|
|
59
|
-
|
60
|
-
#
|
61
|
-
|
62
|
-
|
55
|
+
if sessx
|
56
|
+
# The message body starts with 'Transcript of session follows.'
|
57
|
+
require 'sisimai/smtp/transcript'
|
58
|
+
transcript = Sisimai::SMTP::Transcript.rise(emailparts[0], 'In:', 'Out:')
|
59
|
+
|
60
|
+
return nil unless transcript
|
61
|
+
return nil if transcript.size == 0
|
62
|
+
|
63
|
+
transcript.each do |e|
|
64
|
+
# Pick email addresses, error messages, and the last SMTP command.
|
65
|
+
v ||= dscontents[-1]
|
66
|
+
p = e['response']
|
67
|
+
|
68
|
+
if e['command'] == 'HELO' || e['command'] == 'EHLO'
|
69
|
+
# Use the argument of EHLO/HELO command as a value of "lhost"
|
70
|
+
v['lhost'] = e['argument']
|
71
|
+
|
72
|
+
elsif e['command'] == 'MAIL'
|
73
|
+
# Set the argument of "MAIL" command to pseudo To: header of the original message
|
74
|
+
emailparts[1] += sprintf("To: %s\n", e['argument']) if emailparts[1].size == 0
|
63
75
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
76
|
+
elsif e['command'] == 'RCPT'
|
77
|
+
# RCPT TO: <...>
|
78
|
+
if v['recipient']
|
79
|
+
# There are multiple recipient addresses in the transcript of session
|
80
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
81
|
+
v = dscontents[-1]
|
82
|
+
end
|
83
|
+
v['recipient'] = e['argument']
|
84
|
+
recipients += 1
|
85
|
+
end
|
86
|
+
|
87
|
+
next if p['reply'].to_i < 400
|
88
|
+
commandset << e['command']
|
89
|
+
v['diagnosis'] ||= p['text'].join(' ')
|
90
|
+
v['replycode'] ||= p['reply']
|
91
|
+
v['status'] ||= p['status']
|
68
92
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
#
|
80
|
-
if
|
93
|
+
else
|
94
|
+
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
95
|
+
readcursor = 0 # (Integer) Points the current cursor position
|
96
|
+
|
97
|
+
while e = bodyslices.shift do
|
98
|
+
# Read error messages and delivery status lines from the head of the email to the previous
|
99
|
+
# line of the beginning of the original message.
|
100
|
+
readslices << e # Save the current line for the next loop
|
101
|
+
|
102
|
+
if readcursor == 0
|
103
|
+
# Beginning of the bounce message or message/delivery-status part
|
104
|
+
readcursor |= Indicators[:deliverystatus] if StartingOf[:message].any? { |a| Sisimai::String.aligned(e, a) }
|
105
|
+
next
|
106
|
+
end
|
107
|
+
next if (readcursor & Indicators[:deliverystatus]) == 0
|
108
|
+
next if e.empty?
|
109
|
+
|
110
|
+
if f = Sisimai::RFC1894.match(e)
|
111
|
+
# "e" matched with any field defined in RFC3464
|
112
|
+
next unless o = Sisimai::RFC1894.field(e)
|
113
|
+
v = dscontents[-1]
|
114
|
+
|
115
|
+
if o[-1] == 'addr'
|
81
116
|
# Final-Recipient: rfc822; kijitora@example.jp
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
v
|
117
|
+
# X-Actual-Recipient: rfc822; kijitora@example.co.jp
|
118
|
+
if o[0] == 'final-recipient'
|
119
|
+
# Final-Recipient: rfc822; kijitora@example.jp
|
120
|
+
if v['recipient']
|
121
|
+
# There are multiple recipient addresses in the message body.
|
122
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
123
|
+
v = dscontents[-1]
|
124
|
+
end
|
125
|
+
v['recipient'] = o[2]
|
126
|
+
recipients += 1
|
127
|
+
else
|
128
|
+
# X-Actual-Recipient: rfc822; kijitora@example.co.jp
|
129
|
+
v['alias'] = o[2]
|
86
130
|
end
|
87
|
-
|
88
|
-
|
131
|
+
elsif o[-1] == 'code'
|
132
|
+
# Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
|
133
|
+
v['spec'] = o[1]
|
134
|
+
v['spec'] = 'SMTP' if v['spec'] == 'X-POSTFIX'
|
135
|
+
v['diagnosis'] = o[2]
|
89
136
|
else
|
90
|
-
#
|
91
|
-
|
137
|
+
# Other DSN fields defined in RFC3464
|
138
|
+
next unless fieldtable[o[0]]
|
139
|
+
v[fieldtable[o[0]]] = o[2]
|
140
|
+
|
141
|
+
next unless f
|
142
|
+
permessage[fieldtable[o[0]]] = o[2]
|
92
143
|
end
|
93
|
-
elsif o[-1] == 'code'
|
94
|
-
# Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
|
95
|
-
v['spec'] = o[1]
|
96
|
-
v['spec'] = 'SMTP' if v['spec'] == 'X-POSTFIX'
|
97
|
-
v['diagnosis'] = o[2]
|
98
144
|
else
|
99
|
-
#
|
100
|
-
|
101
|
-
|
145
|
+
# If you do so, please include this problem report. You can
|
146
|
+
# delete your own text from the attached returned message.
|
147
|
+
#
|
148
|
+
# The mail system
|
149
|
+
#
|
150
|
+
# <userunknown@example.co.jp>: host mx.example.co.jp[192.0.2.153] said: 550
|
151
|
+
# 5.1.1 <userunknown@example.co.jp>... User Unknown (in reply to RCPT TO command)
|
152
|
+
if readslices[-2].start_with?('Diagnostic-Code:') && e.include?(' ')
|
153
|
+
# Continued line of the value of Diagnostic-Code header
|
154
|
+
v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
|
155
|
+
readslices[-1] = 'Diagnostic-Code: ' << e
|
102
156
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
else
|
107
|
-
# If you do so, please include this problem report. You can
|
108
|
-
# delete your own text from the attached returned message.
|
109
|
-
#
|
110
|
-
# The mail system
|
111
|
-
#
|
112
|
-
# <userunknown@example.co.jp>: host mx.example.co.jp[192.0.2.153] said: 550
|
113
|
-
# 5.1.1 <userunknown@example.co.jp>... User Unknown (in reply to RCPT TO command)
|
114
|
-
if readslices[-2].start_with?('Diagnostic-Code:') && cv = e.match(/\A[ \t]+(.+)\z/)
|
115
|
-
# Continued line of the value of Diagnostic-Code header
|
116
|
-
v['diagnosis'] << ' ' << cv[1]
|
117
|
-
readslices[-1] = 'Diagnostic-Code: ' << e
|
118
|
-
|
119
|
-
elsif cv = e.match(/\A(X-Postfix-Sender):[ ]*rfc822;[ ]*(.+)\z/)
|
120
|
-
# X-Postfix-Sender: rfc822; shironeko@example.org
|
121
|
-
emailsteak[1] << cv[1] << ': ' << cv[2] << "\n"
|
157
|
+
elsif Sisimai::String.aligned(e, ['X-Postfix-Sender:', 'rfc822;', '@'])
|
158
|
+
# X-Postfix-Sender: rfc822; shironeko@example.org
|
159
|
+
emailparts[1] << 'X-Postfix-Sender: ' << Sisimai::Address.s3s4(e[e.index(';') + 1, e.size]) << "\n"
|
122
160
|
|
123
|
-
else
|
124
|
-
if cv = e.match(/[ \t][(]in reply to (?:end of )?([A-Z]{4}).*/) ||
|
125
|
-
cv = e.match(/([A-Z]{4})[ \t]*.*command[)]\z/)
|
126
|
-
# 5.1.1 <userunknown@example.co.jp>... User Unknown (in reply to RCPT TO
|
127
|
-
commandset << cv[1]
|
128
|
-
anotherset['diagnosis'] ||= ''
|
129
|
-
anotherset['diagnosis'] << ' ' << e
|
130
161
|
else
|
131
162
|
# Alternative error message and recipient
|
132
|
-
if
|
163
|
+
if e.include?(' (in reply to ') || e.include?('command)')
|
164
|
+
# 5.1.1 <userunknown@example.co.jp>... User Unknown (in reply to RCPT TO
|
165
|
+
q = Sisimai::SMTP::Command.find(e); commandset << q if q
|
166
|
+
anotherset['diagnosis'] ||= ''
|
167
|
+
anotherset['diagnosis'] << ' ' << e
|
168
|
+
|
169
|
+
elsif Sisimai::String.aligned(e, ['<', '@', '>', '(expanded from<', '):'])
|
133
170
|
# <r@example.ne.jp> (expanded from <kijitora@example.org>): user ...
|
134
|
-
|
135
|
-
|
136
|
-
|
171
|
+
p1 = e.index('> ')
|
172
|
+
p2 = e.index('(expanded from ', p1)
|
173
|
+
p3 = e.index('>): ', p2 + 14)
|
174
|
+
anotherset['recipient'] = Sisimai::Address.s3s4(e[0, p1])
|
175
|
+
anotherset['alias'] = Sisimai::Address.s3s4(e[p2 + 15, p3 - p2 - 15])
|
176
|
+
anotherset['diagnosis'] = e[p3 + 3, e.size]
|
137
177
|
|
138
|
-
elsif
|
178
|
+
elsif e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>:'])
|
139
179
|
# <kijitora@exmaple.jp>: ...
|
140
|
-
anotherset['recipient'] =
|
141
|
-
anotherset['diagnosis'] =
|
180
|
+
anotherset['recipient'] = Sisimai::Address.s3s4(e[0, e.index('>')])
|
181
|
+
anotherset['diagnosis'] = e[e.index('>:') + 2, e.size]
|
142
182
|
|
143
183
|
elsif e.include?('--- Delivery report unavailable ---')
|
144
184
|
# postfix-3.1.4/src/bounce/bounce_notify_util.c
|
@@ -152,16 +192,17 @@ module Sisimai::Lhost
|
|
152
192
|
# bounce_notify_util.c:602|} else {
|
153
193
|
nomessages = true
|
154
194
|
else
|
155
|
-
# Get error message continued from the previous line
|
195
|
+
# Get an error message continued from the previous line
|
156
196
|
next unless anotherset['diagnosis']
|
157
|
-
if e
|
197
|
+
if e.start_with?(' ')
|
158
198
|
# host mx.example.jp said:...
|
159
|
-
anotherset['diagnosis'] << ' ' << e
|
199
|
+
anotherset['diagnosis'] << ' ' << e[4, e.size]
|
160
200
|
end
|
161
201
|
end
|
162
202
|
end
|
163
203
|
end
|
164
|
-
end
|
204
|
+
end # end of while()
|
205
|
+
|
165
206
|
end
|
166
207
|
|
167
208
|
unless recipients > 0
|
@@ -171,12 +212,13 @@ module Sisimai::Lhost
|
|
171
212
|
dscontents[-1]['recipient'] = anotherset['recipient']
|
172
213
|
recipients += 1
|
173
214
|
else
|
174
|
-
# Get a recipient address from message/rfc822 part if the delivery
|
175
|
-
#
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
215
|
+
# Get a recipient address from message/rfc822 part if the delivery report was unavailable:
|
216
|
+
# '--- Delivery report unavailable ---'
|
217
|
+
p1 = emailparts[1].index("\nTo: ") || -1
|
218
|
+
p2 = emailparts[1].index("\n", p1 + 6) || -1
|
219
|
+
if nomessages && p1 > 0
|
220
|
+
# Try to get a recipient address from To: field in the original message at message/rfc822 part
|
221
|
+
dscontents[-1]['recipient'] = Sisimai::Address.s3s4(emailparts[1][p1 + 5, p2 - p1 - 5])
|
180
222
|
recipients += 1
|
181
223
|
end
|
182
224
|
end
|
@@ -190,50 +232,58 @@ module Sisimai::Lhost
|
|
190
232
|
|
191
233
|
if anotherset['diagnosis']
|
192
234
|
# Copy alternative error message
|
193
|
-
|
235
|
+
anotherset['diagnosis'] = Sisimai::String.sweep(anotherset['diagnosis'])
|
236
|
+
e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].nil? || e['diagnosis'].empty?
|
194
237
|
|
195
238
|
if e['diagnosis'] =~ /\A\d+\z/
|
239
|
+
# Override the value of diagnostic code message
|
196
240
|
e['diagnosis'] = anotherset['diagnosis']
|
197
241
|
else
|
198
242
|
# More detailed error message is in "anotherset"
|
199
|
-
as =
|
200
|
-
ar =
|
243
|
+
as = '' # status
|
244
|
+
ar = '' # replycode
|
201
245
|
|
202
246
|
e['status'] ||= ''
|
203
247
|
e['replycode'] ||= ''
|
204
248
|
|
205
|
-
if e['status']
|
249
|
+
if e['status'].empty? || e['status'].start_with?('4.0.0', '5.0.0')
|
206
250
|
# Check the value of D.S.N. in anotherset
|
207
|
-
as = Sisimai::SMTP::Status.find(anotherset['diagnosis'])
|
208
|
-
if as && as[-
|
251
|
+
as = Sisimai::SMTP::Status.find(anotherset['diagnosis']) || ''
|
252
|
+
if as.size > 0 && as[-4, 4] != '.0.0'
|
209
253
|
# The D.S.N. is neither an empty nor *.0.0
|
210
254
|
e['status'] = as
|
211
255
|
end
|
212
256
|
end
|
213
257
|
|
214
|
-
if e['replycode']
|
215
|
-
# Check the value of SMTP reply code in anotherset
|
216
|
-
ar = Sisimai::SMTP::Reply.find(anotherset['diagnosis'])
|
217
|
-
if ar && ar
|
258
|
+
if e['replycode'].empty? || e['replycode'].end_with?('00')
|
259
|
+
# Check the value of SMTP reply code in $anotherset
|
260
|
+
ar = Sisimai::SMTP::Reply.find(anotherset['diagnosis']) || ''
|
261
|
+
if ar.size > 0 && ar.end_with?('00') == false
|
218
262
|
# The SMTP reply code is neither an empty nor *00
|
219
263
|
e['replycode'] = ar
|
220
264
|
end
|
221
265
|
end
|
222
266
|
|
223
|
-
|
224
|
-
#
|
267
|
+
while true
|
268
|
+
# Replace e['diagnosis'] with the value of anotherset['diagnosis'] when all the
|
269
|
+
# following conditions have not matched.
|
270
|
+
break if (as + ar).size == 0
|
271
|
+
break if anotherset['diagnosis'].size < e['diagnosis'].size
|
272
|
+
break if anotherset['diagnosis'].include?(e['diagnosis']) == false
|
273
|
+
|
225
274
|
e['diagnosis'] = anotherset['diagnosis']
|
275
|
+
break
|
226
276
|
end
|
227
277
|
end
|
228
278
|
end
|
229
279
|
|
230
|
-
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
231
|
-
e['command'] = commandset.shift ||
|
232
|
-
e['command'] ||= 'HELO' if e['diagnosis']
|
233
|
-
e['spec'] ||= 'SMTP' if e['diagnosis']
|
280
|
+
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
|
281
|
+
e['command'] = commandset.shift || Sisimai::SMTP::Command.find(e['diagnosis'])
|
282
|
+
e['command'] ||= 'HELO' if e['diagnosis'].include?('refused to talk to me:')
|
283
|
+
e['spec'] ||= 'SMTP' if Sisimai::String.aligned(e['diagnosis'], ['host ', ' said:'])
|
234
284
|
end
|
235
285
|
|
236
|
-
return { 'ds' => dscontents, 'rfc822' =>
|
286
|
+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
237
287
|
end
|
238
288
|
def description; return 'Postfix'; end
|
239
289
|
end
|
@@ -1,13 +1,12 @@
|
|
1
1
|
module Sisimai::Lhost
|
2
|
-
# Sisimai::Lhost::PowerMTA parses a bounce email which created by
|
3
|
-
#
|
2
|
+
# Sisimai::Lhost::PowerMTA parses a bounce email which created by PowerMTA. Methods in the module
|
3
|
+
# are called from only Sisimai::Message.
|
4
4
|
module PowerMTA
|
5
5
|
class << self
|
6
|
-
# Imported from p5-Sisimail/lib/Sisimai/Lhost/PowerMTA.pm
|
7
6
|
require 'sisimai/lhost'
|
8
7
|
|
9
8
|
Indicators = Sisimai::Lhost.INDICATORS
|
10
|
-
|
9
|
+
Boundaries = ['Content-Type: text/rfc822-headers'].freeze
|
11
10
|
StartingOf = { message: ['Hello, this is the mail server on '] }.freeze
|
12
11
|
Categories = {
|
13
12
|
'bad-domain' => 'hostunknown',
|
@@ -27,23 +26,22 @@ module Sisimai::Lhost
|
|
27
26
|
# @return [Hash] Bounce data list and message/rfc822 part
|
28
27
|
# @return [Nil] it failed to parse or the arguments are missing
|
29
28
|
# @since v4.25.6
|
30
|
-
def
|
29
|
+
def inquire(mhead, mbody)
|
31
30
|
return nil unless mhead['subject'].to_s.start_with?('Delivery report')
|
32
31
|
|
33
|
-
require 'sisimai/rfc1894'
|
34
32
|
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
35
33
|
permessage = {} # (Hash) Store values of each Per-Message field
|
36
34
|
|
37
35
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
38
|
-
|
39
|
-
bodyslices =
|
36
|
+
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
37
|
+
bodyslices = emailparts[0].split("\n")
|
40
38
|
readcursor = 0 # (Integer) Points the current cursor position
|
41
39
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
42
40
|
v = nil
|
43
41
|
|
44
42
|
while e = bodyslices.shift do
|
45
|
-
# Read error messages and delivery status lines from the head of the email
|
46
|
-
#
|
43
|
+
# Read error messages and delivery status lines from the head of the email to the previous
|
44
|
+
# line of the beginning of the original message.
|
47
45
|
if readcursor == 0
|
48
46
|
# Beginning of the bounce message or message/delivery-status part
|
49
47
|
readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
|
@@ -82,7 +80,7 @@ module Sisimai::Lhost
|
|
82
80
|
next unless fieldtable[o[0]]
|
83
81
|
v[fieldtable[o[0]]] = o[2]
|
84
82
|
|
85
|
-
next unless f
|
83
|
+
next unless f
|
86
84
|
permessage[fieldtable[o[0]]] = o[2]
|
87
85
|
end
|
88
86
|
else
|
@@ -96,9 +94,9 @@ module Sisimai::Lhost
|
|
96
94
|
#
|
97
95
|
# <kijitora@example.jp> delivery failed; will not continue trying
|
98
96
|
#
|
99
|
-
if
|
97
|
+
if e.start_with?('X-PowerMTA-BounceCategory: ')
|
100
98
|
# X-PowerMTA-BounceCategory: bad-mailbox
|
101
|
-
v['category'] =
|
99
|
+
v['category'] = e[e.index(':') + 2, e.size]
|
102
100
|
end
|
103
101
|
end
|
104
102
|
end
|
@@ -111,7 +109,7 @@ module Sisimai::Lhost
|
|
111
109
|
e['reason'] = Categories[e['category']] || ''
|
112
110
|
end
|
113
111
|
|
114
|
-
return { 'ds' => dscontents, 'rfc822' =>
|
112
|
+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
115
113
|
end
|
116
114
|
def description; return 'PowerMTA: https://www.sparkpost.com/powermta/'; end
|
117
115
|
end
|