sisimai 4.25.17 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +3 -3
- data/ANALYTICAL-PRECISION +2 -2
- data/Benchmarks.mk +3 -3
- data/CONTRIBUTING +1 -1
- data/ChangeLog.md +406 -407
- data/Developers.mk +5 -6
- data/Gemfile +1 -1
- data/Makefile +12 -12
- data/README-JA.md +142 -94
- data/README.md +282 -150
- data/Rakefile +9 -3
- data/Repository.mk +2 -3
- data/lib/sisimai/address.rb +118 -74
- data/lib/sisimai/arf.rb +84 -82
- data/lib/sisimai/datetime.rb +5 -52
- data/lib/sisimai/{data → fact}/json.rb +7 -9
- data/lib/sisimai/fact/yaml.rb +31 -0
- data/lib/sisimai/fact.rb +468 -0
- data/lib/sisimai/lhost/activehunter.rb +12 -14
- data/lib/sisimai/lhost/amavis.rb +11 -14
- data/lib/sisimai/lhost/amazonses.rb +37 -41
- data/lib/sisimai/lhost/amazonworkmail.rb +15 -18
- data/lib/sisimai/lhost/aol.rb +12 -14
- data/lib/sisimai/lhost/apachejames.rb +19 -21
- data/lib/sisimai/lhost/barracuda.rb +10 -12
- data/lib/sisimai/lhost/bigfoot.rb +21 -21
- data/lib/sisimai/lhost/biglobe.rb +15 -16
- data/lib/sisimai/lhost/courier.rb +20 -20
- data/lib/sisimai/lhost/domino.rb +23 -19
- data/lib/sisimai/lhost/einsundeins.rb +20 -16
- data/lib/sisimai/lhost/exchange2003.rb +30 -29
- data/lib/sisimai/lhost/exchange2007.rb +70 -58
- data/lib/sisimai/lhost/exim.rb +175 -161
- data/lib/sisimai/lhost/ezweb.rb +31 -56
- data/lib/sisimai/lhost/facebook.rb +21 -33
- data/lib/sisimai/lhost/fml.rb +43 -48
- data/lib/sisimai/lhost/gmail.rb +29 -29
- data/lib/sisimai/lhost/gmx.rb +18 -17
- data/lib/sisimai/lhost/googlegroups.rb +9 -10
- data/lib/sisimai/lhost/gsuite.rb +21 -27
- data/lib/sisimai/lhost/imailserver.rb +25 -39
- data/lib/sisimai/lhost/interscanmss.rb +28 -31
- data/lib/sisimai/lhost/kddi.rb +22 -28
- data/lib/sisimai/lhost/mailfoundry.rb +11 -12
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
- data/lib/sisimai/lhost/mailru.rb +33 -27
- data/lib/sisimai/lhost/mcafee.rb +21 -31
- data/lib/sisimai/lhost/messagelabs.rb +17 -20
- data/lib/sisimai/lhost/messagingserver.rb +40 -37
- data/lib/sisimai/lhost/mfilter.rb +15 -16
- data/lib/sisimai/lhost/mxlogic.rb +24 -23
- data/lib/sisimai/lhost/notes.rb +17 -17
- data/lib/sisimai/lhost/office365.rb +63 -27
- data/lib/sisimai/lhost/opensmtpd.rb +12 -13
- data/lib/sisimai/lhost/outlook.rb +12 -15
- data/lib/sisimai/lhost/postfix.rb +179 -129
- data/lib/sisimai/lhost/powermta.rb +12 -14
- data/lib/sisimai/lhost/qmail.rb +44 -47
- data/lib/sisimai/lhost/receivingses.rb +15 -20
- data/lib/sisimai/lhost/sendgrid.rb +34 -32
- data/lib/sisimai/lhost/sendmail.rb +66 -53
- data/lib/sisimai/lhost/surfcontrol.rb +19 -19
- data/lib/sisimai/lhost/v5sendmail.rb +45 -39
- data/lib/sisimai/lhost/verizon.rb +35 -39
- data/lib/sisimai/lhost/x1.rb +18 -17
- data/lib/sisimai/lhost/x2.rb +17 -14
- data/lib/sisimai/lhost/x3.rb +19 -19
- data/lib/sisimai/lhost/x4.rb +72 -57
- data/lib/sisimai/lhost/x5.rb +17 -19
- data/lib/sisimai/lhost/x6.rb +41 -17
- data/lib/sisimai/lhost/yahoo.rb +17 -16
- data/lib/sisimai/lhost/yandex.rb +16 -20
- data/lib/sisimai/lhost/zoho.rb +16 -15
- data/lib/sisimai/lhost.rb +8 -10
- data/lib/sisimai/mail/maildir.rb +1 -3
- data/lib/sisimai/mail/mbox.rb +3 -4
- data/lib/sisimai/mail/memory.rb +0 -1
- data/lib/sisimai/mail/stdin.rb +1 -3
- data/lib/sisimai/mail.rb +3 -7
- data/lib/sisimai/mda.rb +28 -42
- data/lib/sisimai/message.rb +435 -325
- data/lib/sisimai/order.rb +5 -5
- data/lib/sisimai/reason/authfailure.rb +64 -0
- data/lib/sisimai/reason/badreputation.rb +53 -0
- data/lib/sisimai/reason/blocked.rb +94 -160
- data/lib/sisimai/reason/contenterror.rb +8 -9
- data/lib/sisimai/reason/delivered.rb +4 -6
- data/lib/sisimai/reason/exceedlimit.rb +10 -12
- data/lib/sisimai/reason/expired.rb +6 -8
- data/lib/sisimai/reason/feedback.rb +2 -3
- data/lib/sisimai/reason/filtered.rb +17 -19
- data/lib/sisimai/reason/hasmoved.rb +9 -10
- data/lib/sisimai/reason/hostunknown.rb +15 -15
- data/lib/sisimai/reason/mailboxfull.rb +10 -12
- data/lib/sisimai/reason/mailererror.rb +18 -20
- data/lib/sisimai/reason/mesgtoobig.rb +9 -11
- data/lib/sisimai/reason/networkerror.rb +5 -8
- data/lib/sisimai/reason/norelaying.rb +8 -11
- data/lib/sisimai/reason/notaccept.rb +13 -14
- data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
- data/lib/sisimai/reason/onhold.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +14 -12
- data/lib/sisimai/reason/rejected.rb +26 -24
- data/lib/sisimai/reason/requireptr.rb +69 -0
- data/lib/sisimai/reason/securityerror.rb +33 -36
- data/lib/sisimai/reason/spamdetected.rb +114 -147
- data/lib/sisimai/reason/speeding.rb +49 -0
- data/lib/sisimai/reason/suspend.rb +11 -11
- data/lib/sisimai/reason/syntaxerror.rb +11 -10
- data/lib/sisimai/reason/systemerror.rb +7 -9
- data/lib/sisimai/reason/systemfull.rb +7 -8
- data/lib/sisimai/reason/toomanyconn.rb +9 -11
- data/lib/sisimai/reason/undefined.rb +2 -3
- data/lib/sisimai/reason/userunknown.rb +129 -146
- data/lib/sisimai/reason/vacation.rb +3 -4
- data/lib/sisimai/reason/virusdetected.rb +10 -11
- data/lib/sisimai/reason.rb +59 -64
- data/lib/sisimai/rfc1894.rb +55 -28
- data/lib/sisimai/rfc2045.rb +373 -0
- data/lib/sisimai/rfc3464.rb +250 -308
- data/lib/sisimai/rfc3834.rb +42 -45
- data/lib/sisimai/rfc5322.rb +75 -100
- data/lib/sisimai/rfc5965.rb +31 -0
- data/lib/sisimai/rhost/cox.rb +5 -6
- data/lib/sisimai/rhost/franceptt.rb +6 -8
- data/lib/sisimai/rhost/godaddy.rb +12 -12
- data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
- data/lib/sisimai/rhost/iua.rb +9 -10
- data/lib/sisimai/rhost/kddi.rb +6 -8
- data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
- data/lib/sisimai/rhost/mimecast.rb +42 -40
- data/lib/sisimai/rhost/nttdocomo.rb +13 -18
- data/lib/sisimai/rhost/spectrum.rb +10 -12
- data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
- data/lib/sisimai/rhost.rb +23 -31
- data/lib/sisimai/smtp/command.rb +59 -0
- data/lib/sisimai/smtp/error.rb +4 -7
- data/lib/sisimai/smtp/reply.rb +161 -74
- data/lib/sisimai/smtp/status.rb +504 -393
- data/lib/sisimai/smtp/transcript.rb +124 -0
- data/lib/sisimai/smtp.rb +0 -1
- data/lib/sisimai/string.rb +74 -5
- data/lib/sisimai/time.rb +1 -2
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +35 -21
- data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
- data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
- data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
- data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
- data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
- data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
- data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
- data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
- data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
- data/sisimai-java.gemspec +1 -1
- data/sisimai.gemspec +1 -1
- metadata +41 -21
- data/.rspec +0 -2
- data/lib/sisimai/data/yaml.rb +0 -33
- data/lib/sisimai/data.rb +0 -411
- data/lib/sisimai/mime.rb +0 -456
- data/set-of-emails/maildir/mac/reported-from-nick4tech-san-01.eml +0 -6
- /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
@@ -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
|