sisimai 4.25.15 → 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 +419 -388
- 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 -313
- 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 -47
- data/lib/sisimai/rfc5322.rb +75 -100
- data/lib/sisimai/rfc5965.rb +31 -0
- data/lib/sisimai/rhost/cox.rb +5 -6
- data/lib/sisimai/rhost/franceptt.rb +6 -8
- data/lib/sisimai/rhost/godaddy.rb +12 -12
- data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
- data/lib/sisimai/rhost/iua.rb +9 -10
- data/lib/sisimai/rhost/kddi.rb +6 -8
- data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
- data/lib/sisimai/rhost/mimecast.rb +42 -40
- data/lib/sisimai/rhost/nttdocomo.rb +12 -12
- data/lib/sisimai/rhost/spectrum.rb +10 -12
- data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
- data/lib/sisimai/rhost.rb +23 -31
- data/lib/sisimai/smtp/command.rb +59 -0
- data/lib/sisimai/smtp/error.rb +4 -7
- data/lib/sisimai/smtp/reply.rb +161 -74
- data/lib/sisimai/smtp/status.rb +504 -393
- data/lib/sisimai/smtp/transcript.rb +124 -0
- data/lib/sisimai/smtp.rb +0 -1
- data/lib/sisimai/string.rb +74 -5
- data/lib/sisimai/time.rb +1 -2
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +35 -21
- data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
- data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
- data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
- data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
- data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
- data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
- data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
- data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
- data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
- data/sisimai-java.gemspec +1 -1
- data/sisimai.gemspec +1 -1
- metadata +41 -20
- data/.rspec +0 -2
- data/lib/sisimai/data/yaml.rb +0 -33
- data/lib/sisimai/data.rb +0 -411
- data/lib/sisimai/mime.rb +0 -456
- /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
data/lib/sisimai/rfc3464.rb
CHANGED
@@ -1,108 +1,112 @@
|
|
1
1
|
module Sisimai
|
2
2
|
# Sisimai::RFC3464 - bounce mail parser class for Fallback.
|
3
3
|
module RFC3464
|
4
|
-
# Imported from p5-Sisimail/lib/Sisimai/RFC3464.pm
|
5
4
|
class << self
|
6
5
|
require 'sisimai/lhost'
|
6
|
+
require 'sisimai/address'
|
7
|
+
require 'sisimai/rfc1894'
|
7
8
|
|
8
9
|
# http://tools.ietf.org/html/rfc3464
|
9
10
|
Indicators = Sisimai::Lhost.INDICATORS
|
10
|
-
|
11
|
-
message:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
content-type:[ ]*(?:message/rfc822|text/rfc822-headers)
|
27
|
-
|return-path:[ ]*[<].+[>]
|
28
|
-
)\z
|
29
|
-
}x,
|
30
|
-
error: %r/\A(?:[45]\d\d[ \t]+|[<][^@]+[@][^@]+[>]:?[ \t]+)/,
|
31
|
-
command: %r/[ ](RCPT|MAIL|DATA)[ ]+command\b/,
|
11
|
+
StartingOf = {
|
12
|
+
message: [
|
13
|
+
'content-type: message/delivery-status',
|
14
|
+
'content-type: message/disposition-notification',
|
15
|
+
'content-type: text/plain; charset=',
|
16
|
+
'the original message was received at ',
|
17
|
+
'this report relates to your message',
|
18
|
+
'your message could not be delivered',
|
19
|
+
'your message was not delivered to ',
|
20
|
+
'your message was not delivered to the following recipients',
|
21
|
+
],
|
22
|
+
rfc822: [
|
23
|
+
'content-type: message/rfc822',
|
24
|
+
'content-type: text/rfc822-headers',
|
25
|
+
'return-path: <'
|
26
|
+
],
|
32
27
|
}.freeze
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
28
|
+
ReadUntil0 = [
|
29
|
+
# Stop reading when the following string have appeared at the first of a line
|
30
|
+
'a copy of the original message below this line:',
|
31
|
+
'content-type: message/delivery-status',
|
32
|
+
'for further assistance, please contact ',
|
33
|
+
'here is a copy of the first part of the message',
|
34
|
+
'received:',
|
35
|
+
'received-from-mta:',
|
36
|
+
'reporting-mta:',
|
37
|
+
'reporting-ua:',
|
38
|
+
'return-path:',
|
39
|
+
'the non-delivered message is attached to this message',
|
40
|
+
].freeze
|
41
|
+
ReadUntil1 = [
|
42
|
+
# Stop reading when the following string have appeared in a line
|
43
|
+
'attachment is a copy of the message',
|
44
|
+
'below is a copy of the original message:',
|
45
|
+
'below this line is a copy of the message',
|
46
|
+
'message contains ',
|
47
|
+
'message text follows: ',
|
48
|
+
'original message follows',
|
49
|
+
'the attachment contains the original mail headers',
|
50
|
+
'the first ',
|
51
|
+
'unsent message below',
|
52
|
+
'your message reads (in part):',
|
53
|
+
].freeze
|
54
|
+
ReadAfter0 = [
|
55
|
+
# Do not read before the following strings
|
56
|
+
' the postfix ',
|
57
|
+
'a summary of the undelivered message you sent follows:',
|
58
|
+
'the following is the error message',
|
59
|
+
'the message that you sent was undeliverable to the following',
|
60
|
+
'your message was not delivered to ',
|
61
|
+
].freeze
|
62
|
+
DoNotRead0 = [' -----', ' -----', '--', '|--', '*'].freeze
|
63
|
+
DoNotRead1 = ['mail from:', 'message-id:', ' from: '].freeze
|
64
|
+
ReadEmail0 = [' ', '"', '<',].freeze
|
65
|
+
ReadEmail1 = [
|
66
|
+
# There is an email address around the following strings
|
67
|
+
'address:',
|
68
|
+
'addressed to',
|
69
|
+
'could not be delivered to:',
|
70
|
+
'delivered to',
|
71
|
+
'delivery failed:',
|
72
|
+
'did not reach the following recipient:',
|
73
|
+
'error-for:',
|
74
|
+
'failed recipient:',
|
75
|
+
'failed to deliver to',
|
76
|
+
'intended recipient:',
|
77
|
+
'mailbox is full:',
|
78
|
+
'recipient:',
|
79
|
+
'rcpt to:',
|
80
|
+
'smtp server <',
|
81
|
+
'the following recipients returned permanent errors:',
|
82
|
+
'the following addresses had permanent errors',
|
83
|
+
'the following message to',
|
84
|
+
'to: ',
|
85
|
+
'unknown user:',
|
86
|
+
'unable to deliver mail to the following recipient',
|
87
|
+
'undeliverable to',
|
88
|
+
'undeliverable address:',
|
89
|
+
'you sent mail to',
|
90
|
+
'your message has encountered delivery problems to the following recipients:',
|
91
|
+
'was automatically rejected',
|
92
|
+
'was rejected due to',
|
93
|
+
].freeze
|
94
94
|
|
95
95
|
# Detect an error for RFC3464
|
96
96
|
# @param [Hash] mhead Message headers of a bounce email
|
97
97
|
# @param [String] mbody Message body of a bounce email
|
98
98
|
# @return [Hash] Bounce data list and message/rfc822 part
|
99
99
|
# @return [Nil] it failed to parse or the arguments are missing
|
100
|
-
def
|
100
|
+
def inquire(mhead, mbody)
|
101
|
+
fieldtable = Sisimai::RFC1894.FIELDTABLE
|
102
|
+
permessage = {} # (Hash) Store values of each Per-Message field
|
103
|
+
|
101
104
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
102
105
|
bodyslices = mbody.scrub('?').split("\n")
|
103
106
|
readslices = ['']
|
104
107
|
rfc822text = '' # (String) message/rfc822 part text
|
105
108
|
maybealias = nil # (String) Original-Recipient Field
|
109
|
+
lowercased = '' # (String) Lowercased each line of the loop
|
106
110
|
blanklines = 0 # (Integer) The number of blank lines
|
107
111
|
readcursor = 0 # (Integer) Points the current cursor position
|
108
112
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
@@ -115,14 +119,14 @@ module Sisimai
|
|
115
119
|
v = nil
|
116
120
|
|
117
121
|
while e = bodyslices.shift do
|
118
|
-
# Read error messages and delivery status lines from the head of the email
|
119
|
-
#
|
122
|
+
# Read error messages and delivery status lines from the head of the email to the previous
|
123
|
+
# line of the beginning of the original message.
|
120
124
|
readslices << e # Save the current line for the next loop
|
121
|
-
|
125
|
+
lowercased = e.downcase
|
122
126
|
|
123
127
|
if readcursor == 0
|
124
128
|
# Beginning of the bounce message or delivery status part
|
125
|
-
if
|
129
|
+
if StartingOf[:message].any? { |a| lowercased.start_with?(a) }
|
126
130
|
readcursor |= Indicators[:deliverystatus]
|
127
131
|
next
|
128
132
|
end
|
@@ -130,7 +134,7 @@ module Sisimai
|
|
130
134
|
|
131
135
|
if (readcursor & Indicators[:'message-rfc822']) == 0
|
132
136
|
# Beginning of the original message part
|
133
|
-
if
|
137
|
+
if StartingOf[:rfc822].any? { |a| lowercased == a }
|
134
138
|
readcursor |= Indicators[:'message-rfc822']
|
135
139
|
next
|
136
140
|
end
|
@@ -150,189 +154,75 @@ module Sisimai
|
|
150
154
|
next if e.empty?
|
151
155
|
|
152
156
|
v = dscontents[-1]
|
153
|
-
if
|
154
|
-
#
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
157
|
+
if f = Sisimai::RFC1894.match(e)
|
158
|
+
# "e" matched with any field defined in RFC3464
|
159
|
+
next unless o = Sisimai::RFC1894.field(e)
|
160
|
+
|
161
|
+
if o[-1] == 'addr'
|
162
|
+
# Final-Recipient: rfc822; kijitora@example.jp
|
163
|
+
# X-Actual-Recipient: rfc822; kijitora@example.co.jp
|
164
|
+
if o[0] == 'final-recipient' || o[0] == 'original-recipient'
|
165
|
+
# Final-Recipient: rfc822; kijitora@example.jp
|
166
|
+
if o[0] == 'original-recipient'
|
167
|
+
# Original-Recipient: ...
|
168
|
+
maybealias = o[2]
|
169
|
+
else
|
170
|
+
# Final-Recipient: ...
|
171
|
+
x = v['recipient'] || ''
|
172
|
+
y = Sisimai::Address.s3s4(o[2])
|
173
|
+
y = maybealias unless Sisimai::Address.is_emailaddress(y)
|
174
|
+
|
175
|
+
if !x.empty? && x != y
|
176
|
+
# There are multiple recipient addresses in the message body.
|
177
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
178
|
+
v = dscontents[-1]
|
179
|
+
end
|
180
|
+
v['recipient'] = y
|
181
|
+
recipients += 1
|
182
|
+
itisbounce ||= true
|
183
|
+
|
184
|
+
v['alias'] ||= maybealias
|
185
|
+
maybealias = nil
|
186
|
+
end
|
187
|
+
elsif o[0] == 'x-actual-recipient'
|
188
|
+
# X-Actual-Recipient: RFC822; |IFS=' ' && exec procmail -f- || exit 75 ...
|
189
|
+
# X-Actual-Recipient: rfc822; kijitora@neko.example.jp
|
190
|
+
v['alias'] = o[2] unless o[2].include?(' ')
|
185
191
|
end
|
186
|
-
|
187
|
-
|
188
|
-
|
192
|
+
elsif o[-1] == 'code'
|
193
|
+
# Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
|
194
|
+
v['spec'] = o[1]
|
195
|
+
v['diagnosis'] = o[2]
|
196
|
+
else
|
197
|
+
# Other DSN fields defined in RFC3464
|
198
|
+
next unless fieldtable[o[0]]
|
199
|
+
v[fieldtable[o[0]]] = o[2]
|
189
200
|
|
190
|
-
|
191
|
-
|
201
|
+
next unless f
|
202
|
+
permessage[fieldtable[o[0]]] = o[2]
|
192
203
|
end
|
193
|
-
|
194
|
-
elsif cv = e.match(/\AX-Actual-Recipient:[ ]*(?:RFC|rfc)822;[ ]*([^ ]+)\z/)
|
195
|
-
# X-Actual-Recipient: RFC822; |IFS=' ' && exec procmail -f- || exit 75 ...
|
196
|
-
# X-Actual-Recipient: rfc822; kijitora@neko.example.jp
|
197
|
-
v['alias'] = cv[1] unless cv[1] =~ /[ \t]+/
|
198
|
-
|
199
|
-
elsif cv = e.match(/\AAction:[ ]*(.+)\z/)
|
200
|
-
# 2.3.3 Action field
|
201
|
-
# The Action field indicates the action performed by the Reporting-MTA
|
202
|
-
# as a result of its attempt to deliver the message to this recipient
|
203
|
-
# address. This field MUST be present for each recipient named in the
|
204
|
-
# DSN.
|
205
|
-
# The syntax for the action-field is:
|
206
|
-
#
|
207
|
-
# action-field = "Action" ":" action-value
|
208
|
-
# action-value =
|
209
|
-
# "failed" / "delayed" / "delivered" / "relayed" / "expanded"
|
210
|
-
#
|
211
|
-
# The action-value may be spelled in any combination of upper and lower
|
212
|
-
# case characters.
|
213
|
-
v['action'] = cv[1].downcase
|
214
|
-
|
215
|
-
# failed (bad destination mailbox address)
|
216
|
-
if cv = v['action'].match(/\A([^ ]+)[ ]/) then v['action'] = cv[1] end
|
217
|
-
|
218
|
-
elsif cv = e.match(/\AStatus:[ ]*(\d[.]\d+[.]\d+)/)
|
219
|
-
# 2.3.4 Status field
|
220
|
-
# The per-recipient Status field contains a transport-independent
|
221
|
-
# status code that indicates the delivery status of the message to that
|
222
|
-
# recipient. This field MUST be present for each delivery attempt
|
223
|
-
# which is described by a DSN.
|
224
|
-
#
|
225
|
-
# The syntax of the status field is:
|
226
|
-
#
|
227
|
-
# status-field = "Status" ":" status-code
|
228
|
-
# status-code = DIGIT "." 1*3DIGIT "." 1*3DIGIT
|
229
|
-
v['status'] = cv[1]
|
230
|
-
|
231
|
-
elsif cv = e.match(/\AStatus:[ ]*(\d+[ ]+.+)\z/)
|
232
|
-
# Status: 553 Exceeded maximum inbound message size
|
233
|
-
v['alterrors'] = cv[1]
|
234
|
-
|
235
|
-
elsif cv = e.match(/\ARemote-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
|
236
|
-
# 2.3.5 Remote-MTA field
|
237
|
-
# The value associated with the Remote-MTA DSN field is a printable
|
238
|
-
# ASCII representation of the name of the "remote" MTA that reported
|
239
|
-
# delivery status to the "reporting" MTA.
|
240
|
-
#
|
241
|
-
# remote-mta-field = "Remote-MTA" ":" mta-name-type ";" mta-name
|
242
|
-
#
|
243
|
-
# NOTE: The Remote-MTA field preserves the "while talking to"
|
244
|
-
# information that was provided in some pre-existing nondelivery
|
245
|
-
# reports.
|
246
|
-
#
|
247
|
-
# This field is optional. It MUST NOT be included if no remote MTA was
|
248
|
-
# involved in the attempted delivery of the message to that recipient.
|
249
|
-
v['rhost'] = cv[1].downcase
|
250
|
-
|
251
|
-
elsif cv = e.match(/\ALast-Attempt-Date:[ ]*(.+)\z/)
|
252
|
-
# 2.3.7 Last-Attempt-Date field
|
253
|
-
# The Last-Attempt-Date field gives the date and time of the last
|
254
|
-
# attempt to relay, gateway, or deliver the message (whether successful
|
255
|
-
# or unsuccessful) by the Reporting MTA. This is not necessarily the
|
256
|
-
# same as the value of the Date field from the header of the message
|
257
|
-
# used to transmit this delivery status notification: In cases where
|
258
|
-
# the DSN was generated by a gateway, the Date field in the message
|
259
|
-
# header contains the time the DSN was sent by the gateway and the DSN
|
260
|
-
# Last-Attempt-Date field contains the time the last delivery attempt
|
261
|
-
# occurred.
|
262
|
-
#
|
263
|
-
# last-attempt-date-field = "Last-Attempt-Date" ":" date-time
|
264
|
-
v['date'] = cv[1]
|
265
204
|
else
|
266
|
-
|
267
|
-
|
268
|
-
#
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
#
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
v['spec'] = cv[1].upcase
|
277
|
-
v['diagnosis'] = cv[2]
|
278
|
-
|
279
|
-
elsif cv = e.match(/\ADiagnostic-Code:[ ]*(.+)\z/)
|
280
|
-
# No value of "diagnostic-type"
|
281
|
-
# Diagnostic-Code: 554 ...
|
282
|
-
v['diagnosis'] = cv[1]
|
283
|
-
|
284
|
-
elsif readslices[-2].start_with?('Diagnostic-Code:') && cv = e.match(/\A[ \t]+(.+)\z/)
|
205
|
+
# The line did not match with any fields defined in RFC3464
|
206
|
+
if e.start_with?('Diagnostic-Code: ') && e.include?(';') == false
|
207
|
+
# There is no value of "diagnostic-type" such as Diagnostic-Code: 554 ...
|
208
|
+
v['diagnosis'] = e[e.index(' ') + 1, e.size]
|
209
|
+
|
210
|
+
elsif e.start_with?('Status: ') && Sisimai::SMTP::Reply.find(e[8, 3])
|
211
|
+
# Status: 553 Exceeded maximum inbound message size
|
212
|
+
v['alterrors'] = e[8, e.size]
|
213
|
+
|
214
|
+
elsif readslices[-2].start_with?('Diagnostic-Code:') && cv = e.start_with?(' ')
|
285
215
|
# Continued line of the value of Diagnostic-Code header
|
286
|
-
v['diagnosis'] << ' ' <<
|
216
|
+
v['diagnosis'] << ' ' << e
|
287
217
|
readslices[-1] = 'Diagnostic-Code: ' << e
|
288
218
|
else
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
# The Reporting-MTA field is defined as follows:
|
297
|
-
#
|
298
|
-
# A DSN describes the results of attempts to deliver, relay, or gateway
|
299
|
-
# a message to one or more recipients. In all cases, the Reporting-MTA
|
300
|
-
# is the MTA that attempted to perform the delivery, relay, or gateway
|
301
|
-
# operation described in the DSN. This field is required.
|
302
|
-
connheader['rhost'] ||= cv[1].downcase
|
303
|
-
|
304
|
-
elsif cv = e.match(/\AReceived-From-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
|
305
|
-
# 2.2.4 The Received-From-MTA DSN field
|
306
|
-
# The optional Received-From-MTA field indicates the name of the MTA
|
307
|
-
# from which the message was received.
|
308
|
-
#
|
309
|
-
# received-from-mta-field =
|
310
|
-
# "Received-From-MTA" ":" mta-name-type ";" mta-name
|
311
|
-
#
|
312
|
-
# If the message was received from an Internet host via SMTP, the
|
313
|
-
# contents of the mta-name sub-field SHOULD be the Internet domain name
|
314
|
-
# supplied in the HELO or EHLO command, and the network address used by
|
315
|
-
# the SMTP client SHOULD be included as a comment enclosed in
|
316
|
-
# parentheses. (In this case, the MTA-name-type will be "dns".)
|
317
|
-
connheader['lhost'] = cv[1].downcase
|
318
|
-
|
319
|
-
elsif cv = e.match(/\AArrival-Date:[ ]*(.+)\z/)
|
320
|
-
# 2.2.5 The Arrival-Date DSN field
|
321
|
-
# The optional Arrival-Date field indicates the date and time at which
|
322
|
-
# the message arrived at the Reporting MTA. If the Last-Attempt-Date
|
323
|
-
# field is also provided in a per-recipient field, this can be used to
|
324
|
-
# determine the interval between when the message arrived at the
|
325
|
-
# Reporting MTA and when the report was issued for that recipient.
|
326
|
-
#
|
327
|
-
# arrival-date-field = "Arrival-Date" ":" date-time
|
328
|
-
connheader['date'] = cv[1]
|
329
|
-
else
|
330
|
-
# Get error message
|
331
|
-
next if e.start_with?(' ', '-')
|
332
|
-
next unless e =~ MarkingsOf[:error]
|
333
|
-
|
334
|
-
# 500 User Unknown
|
335
|
-
# <kijitora@example.jp> Unknown
|
219
|
+
# Get error messages which is written in the message body directly
|
220
|
+
next if e.start_with?(' ', ' ', 'X')
|
221
|
+
cr = Sisimai::SMTP::Reply.find(e) || ''
|
222
|
+
ca = Sisimai::Address.find(e) || []
|
223
|
+
co = Sisimai::String.aligned(e, ['<', '@', '>'])
|
224
|
+
|
225
|
+
if cr.size > 0 || (ca.size > 0 && co)
|
336
226
|
v['alterrors'] ||= ' '
|
337
227
|
v['alterrors'] << ' ' << e
|
338
228
|
end
|
@@ -341,50 +231,101 @@ module Sisimai
|
|
341
231
|
end # End of if: rfc822
|
342
232
|
end
|
343
233
|
|
234
|
+
# -----------------------------------------------------------------------------------------
|
344
235
|
while true
|
345
236
|
# Fallback, parse entire message body
|
346
237
|
break if recipients > 0
|
347
|
-
match = 0
|
348
|
-
mfrom = mhead['from'].downcase
|
349
238
|
|
350
239
|
# Failed to get a recipient address at code above
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
end
|
368
|
-
break unless match > 0
|
240
|
+
returnpath = (mhead['return-path'] || '').downcase
|
241
|
+
headerfrom = (mhead['from'] || '').downcase
|
242
|
+
errortitle = (mhead['subject'] || '').downcase
|
243
|
+
patternsof = {
|
244
|
+
'from' => ['postmaster@', 'mailer-daemon@', 'root@'],
|
245
|
+
'return-path' => ['<>', 'mailer-daemon'],
|
246
|
+
'subject' => ['delivery fail', 'delivery report', 'failure notice', 'mail delivery',
|
247
|
+
'mail failed', 'mail error', 'non-delivery', 'returned mail',
|
248
|
+
'undeliverable mail', 'warning: '],
|
249
|
+
}
|
250
|
+
|
251
|
+
match = nil
|
252
|
+
match ||= patternsof['from'].any? { |v| headerfrom.include?(v) }
|
253
|
+
match ||= patternsof['subject'].any? { |v| errortitle.include?(v) }
|
254
|
+
match ||= patternsof['return-path'].any? { |v| returnpath.include?(v) }
|
255
|
+
break unless match
|
369
256
|
|
370
257
|
b = dscontents[-1]
|
258
|
+
hasmatched = 0 # There may be an email address around the line
|
259
|
+
readslices = [] # Previous line of this loop
|
260
|
+
|
261
|
+
ReadAfter0.each do |e|
|
262
|
+
# Cut strings from the begining of "mbody" to the strings defined in ReadAfter0
|
263
|
+
i = mbody.downcase.index(e)
|
264
|
+
next unless i
|
265
|
+
mbody = mbody[i, mbody.size - i]
|
266
|
+
end
|
267
|
+
lowercased = mbody.downcase
|
371
268
|
bodyslices = mbody.split("\n")
|
269
|
+
|
372
270
|
while e = bodyslices.shift do
|
373
271
|
# Get the recipient's email address and error messages.
|
374
|
-
d = e.downcase
|
375
|
-
break if d =~ MarkingsOf[:rfc822]
|
376
|
-
break if d =~ ReStop
|
377
|
-
|
378
272
|
next if e.empty?
|
379
|
-
|
380
|
-
|
273
|
+
hasmatched = 0
|
274
|
+
lowercased = e.downcase
|
275
|
+
readslices << lowercased
|
276
|
+
|
277
|
+
break if StartingOf[:rfc822].include?(lowercased)
|
278
|
+
break if ReadUntil0.any? { |v| lowercased.start_with?(v) }
|
279
|
+
break if ReadUntil1.any? { |v| lowercased.include?(v) }
|
280
|
+
next if DoNotRead0.any? { |v| lowercased.start_with?(v) }
|
281
|
+
next if DoNotRead1.any? { |v| lowercased.include?(v) }
|
282
|
+
|
283
|
+
while true do
|
284
|
+
# There is an email address with an error message at this line(1)
|
285
|
+
break unless ReadEmail0.any? { |v| lowercased.start_with?(v) }
|
286
|
+
break unless lowercased.include?('@')
|
287
|
+
|
288
|
+
hasmatched = 1
|
289
|
+
break
|
290
|
+
end
|
291
|
+
|
292
|
+
while true do
|
293
|
+
# There is an email address with an error message at this line(2)
|
294
|
+
break if hasmatched > 0
|
295
|
+
break unless ReadEmail1.any? { |v| lowercased.include?(v) }
|
296
|
+
break unless lowercased.include?('@')
|
297
|
+
|
298
|
+
hasmatched = 2
|
299
|
+
break
|
300
|
+
end
|
381
301
|
|
382
|
-
|
302
|
+
while true do
|
303
|
+
# There is an email address without an error message at this line
|
304
|
+
break if hasmatched > 0
|
305
|
+
break if readslices.size < 2
|
306
|
+
break unless ReadEmail1.any? { |v| readslices[-2].include?(v) }
|
307
|
+
break unless lowercased.include?('@') # Must contain '@'
|
308
|
+
break unless lowercased.include?('.') # Must contain '.'
|
309
|
+
break if lowercased.include?('$')
|
310
|
+
|
311
|
+
hasmatched = 3
|
312
|
+
break
|
313
|
+
end
|
314
|
+
|
315
|
+
if hasmatched > 0 && lowercased.include?('@')
|
383
316
|
# May be an email address
|
317
|
+
w = e.split(' ')
|
384
318
|
x = b['recipient'] || ''
|
385
|
-
y =
|
386
|
-
|
387
|
-
|
319
|
+
y = ''
|
320
|
+
|
321
|
+
w.each do |ee|
|
322
|
+
# Find an email address (including "@")
|
323
|
+
next unless ee.include?('@')
|
324
|
+
y = Sisimai::Address.s3s4(ee)
|
325
|
+
next unless Sisimai::Address.is_emailaddress(y)
|
326
|
+
break
|
327
|
+
end
|
328
|
+
|
388
329
|
if !x.empty? && x != y
|
389
330
|
# There are multiple recipient addresses in the message body.
|
390
331
|
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
@@ -394,9 +335,9 @@ module Sisimai
|
|
394
335
|
recipients += 1
|
395
336
|
itisbounce ||= true
|
396
337
|
|
397
|
-
elsif
|
338
|
+
elsif e.include?('(expanded from') || e.include?('(generated from')
|
398
339
|
# (expanded from: neko@example.jp)
|
399
|
-
b['alias'] = Sisimai::Address.s3s4(
|
340
|
+
b['alias'] = Sisimai::Address.s3s4(e[e.rindex(' ') + 1, e.size])
|
400
341
|
end
|
401
342
|
b['diagnosis'] ||= ''
|
402
343
|
b['diagnosis'] << ' ' << e
|
@@ -406,9 +347,11 @@ module Sisimai
|
|
406
347
|
end
|
407
348
|
return nil unless itisbounce
|
408
349
|
|
409
|
-
|
350
|
+
p1 = rfc822text.index("\nTo: ") || -1
|
351
|
+
p2 = rfc822text.index("\n", p1 + 6) || -1
|
352
|
+
if recipients == 0 && p1 > 0
|
410
353
|
# Try to get a recipient address from "To:" header of the original message
|
411
|
-
if r = Sisimai::Address.find(
|
354
|
+
if r = Sisimai::Address.find(rfc822text[p1 + 5, p2 - p1 - 5], true)
|
412
355
|
# Found a recipient address
|
413
356
|
dscontents << Sisimai::Lhost.DELIVERYSTATUS if dscontents.size == recipients
|
414
357
|
b = dscontents[-1]
|
@@ -418,8 +361,9 @@ module Sisimai
|
|
418
361
|
end
|
419
362
|
return nil unless recipients > 0
|
420
363
|
|
364
|
+
require 'sisimai/smtp/command'
|
421
365
|
require 'sisimai/mda'
|
422
|
-
mdabounced = Sisimai::MDA.
|
366
|
+
mdabounced = Sisimai::MDA.inquire(mhead, mbody)
|
423
367
|
dscontents.each do |e|
|
424
368
|
# Set default values if each value is empty.
|
425
369
|
connheader.each_key { |a| e[a] ||= connheader[a] || '' }
|
@@ -438,18 +382,16 @@ module Sisimai
|
|
438
382
|
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
|
439
383
|
|
440
384
|
if mdabounced
|
441
|
-
# Make bounce data by the values returned from Sisimai::MDA.
|
385
|
+
# Make bounce data by the values returned from Sisimai::MDA.inquire()
|
442
386
|
e['agent'] = mdabounced['mda'] || 'RFC3464'
|
443
387
|
e['reason'] = mdabounced['reason'] || 'undefined'
|
444
388
|
e['diagnosis'] = mdabounced['message'] unless mdabounced['message'].empty?
|
445
|
-
e['command'] =
|
389
|
+
e['command'] = nil
|
446
390
|
end
|
447
391
|
|
448
|
-
e['
|
449
|
-
|
450
|
-
|
451
|
-
end
|
452
|
-
e['date'] ||= mhead['date']
|
392
|
+
e['date'] ||= mhead['date']
|
393
|
+
e['status'] ||= Sisimai::SMTP::Status.find(e['diagnosis']) || ''
|
394
|
+
e['command'] ||= Sisimai::SMTP::Command.find(e['diagnosis'])
|
453
395
|
end
|
454
396
|
|
455
397
|
return { 'ds' => dscontents, 'rfc822' => rfc822text }
|