sisimai 5.5.0 → 5.7.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/.github/workflows/rake-test.yml +0 -4
- data/ChangeLog.md +53 -0
- data/LICENSE +1 -1
- data/README-JA.md +26 -20
- data/README.md +30 -25
- data/lib/sisimai/address.rb +8 -31
- data/lib/sisimai/arf.rb +3 -5
- data/lib/sisimai/fact.rb +26 -36
- data/lib/sisimai/lhost/activehunter.rb +0 -2
- data/lib/sisimai/lhost/amazonses.rb +7 -9
- data/lib/sisimai/lhost/apachejames.rb +0 -1
- data/lib/sisimai/lhost/biglobe.rb +0 -16
- data/lib/sisimai/lhost/courier.rb +5 -4
- data/lib/sisimai/lhost/deutschetelekom.rb +120 -0
- data/lib/sisimai/lhost/domino.rb +0 -3
- data/lib/sisimai/lhost/dragonfly.rb +0 -27
- data/lib/sisimai/lhost/einsundeins.rb +1 -10
- data/lib/sisimai/lhost/exchange2003.rb +4 -4
- data/lib/sisimai/lhost/exchange2007.rb +5 -6
- data/lib/sisimai/lhost/exim.rb +35 -85
- data/lib/sisimai/lhost/ezweb.rb +12 -49
- data/lib/sisimai/lhost/fml.rb +4 -29
- data/lib/sisimai/lhost/gmail.rb +0 -23
- data/lib/sisimai/lhost/gmx.rb +7 -24
- data/lib/sisimai/lhost/googlegroups.rb +3 -3
- data/lib/sisimai/lhost/googleworkspace.rb +0 -4
- data/lib/sisimai/lhost/imailserver.rb +3 -9
- data/lib/sisimai/lhost/kddi.rb +6 -20
- data/lib/sisimai/lhost/mailfoundry.rb +0 -2
- data/lib/sisimai/lhost/mailmarshal.rb +1 -3
- data/lib/sisimai/lhost/messagingserver.rb +4 -15
- data/lib/sisimai/lhost/mfilter.rb +0 -1
- data/lib/sisimai/lhost/mimecast.rb +0 -1
- data/lib/sisimai/lhost/notes.rb +1 -2
- data/lib/sisimai/lhost/opensmtpd.rb +0 -40
- data/lib/sisimai/lhost/postfix.rb +10 -11
- data/lib/sisimai/lhost/qmail.rb +14 -81
- data/lib/sisimai/lhost/sendmail.rb +4 -4
- data/lib/sisimai/lhost/trendmicro.rb +3 -3
- data/lib/sisimai/lhost/v5sendmail.rb +0 -1
- data/lib/sisimai/lhost/verizon.rb +1 -2
- data/lib/sisimai/lhost/x1.rb +1 -2
- data/lib/sisimai/lhost/x2.rb +0 -2
- data/lib/sisimai/lhost/x3.rb +4 -9
- data/lib/sisimai/lhost/x6.rb +0 -1
- data/lib/sisimai/lhost/zoho.rb +0 -12
- data/lib/sisimai/lhost.rb +38 -19
- data/lib/sisimai/message.rb +3 -1
- data/lib/sisimai/order.rb +4 -1
- data/lib/sisimai/reason/authfailure.rb +9 -13
- data/lib/sisimai/reason/badreputation.rb +7 -7
- data/lib/sisimai/reason/blocked.rb +57 -83
- data/lib/sisimai/reason/contenterror.rb +16 -8
- data/lib/sisimai/reason/{mesgtoobig.rb → emailtoolarge.rb} +22 -25
- data/lib/sisimai/reason/expired.rb +27 -23
- data/lib/sisimai/reason/filtered.rb +13 -17
- data/lib/sisimai/reason/hasmoved.rb +2 -1
- data/lib/sisimai/reason/hostunknown.rb +25 -19
- data/lib/sisimai/reason/mailboxfull.rb +28 -49
- data/lib/sisimai/reason/networkerror.rb +28 -16
- data/lib/sisimai/reason/norelaying.rb +21 -20
- data/lib/sisimai/reason/notaccept.rb +13 -8
- data/lib/sisimai/reason/notcompliantrfc.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +17 -26
- data/lib/sisimai/reason/ratelimited.rb +62 -0
- data/lib/sisimai/reason/rejected.rb +51 -59
- data/lib/sisimai/reason/requireptr.rb +13 -25
- data/lib/sisimai/reason/securityerror.rb +14 -19
- data/lib/sisimai/reason/spamdetected.rb +51 -101
- data/lib/sisimai/reason/suspend.rb +30 -24
- data/lib/sisimai/reason/syntaxerror.rb +1 -8
- data/lib/sisimai/reason/systemerror.rb +37 -23
- data/lib/sisimai/reason/systemfull.rb +1 -1
- data/lib/sisimai/reason/userunknown.rb +81 -112
- data/lib/sisimai/reason/virusdetected.rb +6 -8
- data/lib/sisimai/reason.rb +15 -15
- data/lib/sisimai/rfc1123.rb +1 -1
- data/lib/sisimai/rfc1894.rb +7 -6
- data/lib/sisimai/rfc2045.rb +2 -2
- data/lib/sisimai/rfc3464/thirdparty.rb +1 -1
- data/lib/sisimai/rfc3464.rb +10 -14
- data/lib/sisimai/rfc3834.rb +3 -4
- data/lib/sisimai/rfc791.rb +3 -38
- data/lib/sisimai/rhost/apple.rb +5 -5
- data/lib/sisimai/rhost/cloudflare.rb +2 -0
- data/lib/sisimai/rhost/cox.rb +22 -20
- data/lib/sisimai/rhost/facebook.rb +16 -16
- data/lib/sisimai/rhost/franceptt.rb +8 -3
- data/lib/sisimai/rhost/godaddy.rb +33 -15
- data/lib/sisimai/rhost/google.rb +63 -64
- data/lib/sisimai/rhost/iua.rb +1 -1
- data/lib/sisimai/rhost/messagelabs.rb +12 -12
- data/lib/sisimai/rhost/microsoft.rb +86 -86
- data/lib/sisimai/rhost/mimecast.rb +34 -34
- data/lib/sisimai/rhost/nttdocomo.rb +2 -2
- data/lib/sisimai/rhost/spectrum.rb +7 -7
- data/lib/sisimai/rhost/tencent.rb +9 -11
- data/lib/sisimai/rhost/yahooinc.rb +7 -8
- data/lib/sisimai/rhost.rb +1 -1
- data/lib/sisimai/smtp/command.rb +2 -0
- data/lib/sisimai/smtp/status.rb +73 -109
- data/lib/sisimai/string.rb +0 -27
- data/lib/sisimai/version.rb +1 -1
- data/set-of-emails/maildir/bsd/lhost-deutschetelekom-01.eml +66 -0
- data/set-of-emails/maildir/bsd/lhost-deutschetelekom-02.eml +68 -0
- data/set-of-emails/maildir/bsd/lhost-deutschetelekom-03.eml +50 -0
- data/set-of-emails/should-not-crash/p5-664-iomart-mail-filter.eml +258 -0
- data/set-of-emails/to-be-debugged-because/sisimai-cannot-parse-yet/.gitkeep +0 -0
- metadata +10 -6
- data/lib/sisimai/reason/exceedlimit.rb +0 -47
- data/lib/sisimai/reason/speeding.rb +0 -47
- data/lib/sisimai/reason/toomanyconn.rb +0 -59
|
@@ -63,8 +63,6 @@ module Sisimai::Lhost
|
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
return nil if recipients == 0
|
|
66
|
-
|
|
67
|
-
dscontents.each { |e| e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) }
|
|
68
66
|
return {"ds" => dscontents, "rfc822" => emailparts[1]}
|
|
69
67
|
end
|
|
70
68
|
def description; return 'TransWARE Active!hunter'; end
|
|
@@ -49,7 +49,7 @@ module Sisimai::Lhost
|
|
|
49
49
|
"OnAccountSuppressionList" => "suppressed",
|
|
50
50
|
"General" => "onhold",
|
|
51
51
|
"MailboxFull" => "mailboxfull",
|
|
52
|
-
"MessageTooLarge" => "
|
|
52
|
+
"MessageTooLarge" => "emailtoolarge",
|
|
53
53
|
"ContentRejected" => "contenterror",
|
|
54
54
|
"AttachmentRejected" => "securityerror",
|
|
55
55
|
}.freeze
|
|
@@ -124,7 +124,8 @@ module Sisimai::Lhost
|
|
|
124
124
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
|
125
125
|
whatnotify = jsonobject["notificationType"][0, 1] || ""
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
case whatnotify
|
|
128
|
+
when "B"
|
|
128
129
|
# "notificationType":"Bounce"
|
|
129
130
|
p = jsonobject["bounce"]
|
|
130
131
|
r = p["bounceType"] == "Permanent" ? "5" : "4"
|
|
@@ -137,7 +138,7 @@ module Sisimai::Lhost
|
|
|
137
138
|
v = dscontents[-1]
|
|
138
139
|
end
|
|
139
140
|
v["recipient"] = e["emailAddress"]
|
|
140
|
-
v["diagnosis"] =
|
|
141
|
+
v["diagnosis"] = e["diagnosticCode"]
|
|
141
142
|
v["command"] = Sisimai::SMTP::Command.find(v["diagnosis"])
|
|
142
143
|
v["action"] = e["action"]
|
|
143
144
|
v["status"] = Sisimai::SMTP::Status.find(v["diagnosis"], r)
|
|
@@ -152,8 +153,7 @@ module Sisimai::Lhost
|
|
|
152
153
|
next if ReasonPair[f] != p["bounceSubType"]
|
|
153
154
|
v["reason"] = f; break
|
|
154
155
|
end
|
|
155
|
-
|
|
156
|
-
elsif whatnotify == "C"
|
|
156
|
+
when "C"
|
|
157
157
|
# "notificationType":"Complaint"
|
|
158
158
|
p = jsonobject["complaint"]
|
|
159
159
|
p["complainedRecipients"].each do |e|
|
|
@@ -170,8 +170,7 @@ module Sisimai::Lhost
|
|
|
170
170
|
v["diagnosis"] = sprintf('"feedbackid":"%s", "useragent":"%s"}', p["feedbackId"], p["userAgent"])
|
|
171
171
|
recipients += 1
|
|
172
172
|
end
|
|
173
|
-
|
|
174
|
-
elsif whatnotify == "D"
|
|
173
|
+
when "D"
|
|
175
174
|
# "notificationType":"Delivery"
|
|
176
175
|
p = jsonobject["delivery"]
|
|
177
176
|
p["recipients"].each do |e|
|
|
@@ -186,13 +185,12 @@ module Sisimai::Lhost
|
|
|
186
185
|
v["action"] = "delivered"
|
|
187
186
|
v["date"] = p["timestamp"]
|
|
188
187
|
v["lhost"] = Sisimai::RFC1123.find(p["reportingMTA"])
|
|
189
|
-
v["diagnosis"] =
|
|
188
|
+
v["diagnosis"] = p["smtpResponse"]
|
|
190
189
|
v["command"] = Sisimai::SMTP::Command.find(v["diagnosis"])
|
|
191
190
|
v["status"] = Sisimai::SMTP::Status.find(v["diagnosis"], "2")
|
|
192
191
|
v["replycode"] = Sisimai::SMTP::Reply.find(v["diagnosis"], "2")
|
|
193
192
|
recipients += 1
|
|
194
193
|
end
|
|
195
|
-
|
|
196
194
|
else
|
|
197
195
|
# Unknown "notificationType" value
|
|
198
196
|
warn sprintf(" ***warning: There is no notificationType field or unknown type of notificationType field")
|
|
@@ -103,7 +103,6 @@ module Sisimai::Lhost
|
|
|
103
103
|
emailparts[1] += sprintf("Subject: %s\n", alternates[3]) if alternates[3] != ""
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
dscontents.each { |e| e["diagnosis"] = Sisimai::String.sweep(e["diagnosis"]) }
|
|
107
106
|
return { "ds" => dscontents, "rfc822" => emailparts[1] }
|
|
108
107
|
end
|
|
109
108
|
def description; return 'Java Apache Mail Enterprise Server'; end
|
|
@@ -11,10 +11,6 @@ module Sisimai::Lhost
|
|
|
11
11
|
message: [' ----- The following addresses had delivery problems -----'],
|
|
12
12
|
error: [' ----- Non-delivered information -----'],
|
|
13
13
|
}.freeze
|
|
14
|
-
MessagesOf = {
|
|
15
|
-
'filtered' => ['Mail Delivery Failed... User unknown'],
|
|
16
|
-
'mailboxfull' => ["The number of messages in recipient's mailbox exceeded the local limit."],
|
|
17
|
-
}.freeze
|
|
18
14
|
|
|
19
15
|
# @asbtract Decodes the bounce message from Biglobe
|
|
20
16
|
# @param [Hash] mhead Message headers of a bounce email
|
|
@@ -76,18 +72,6 @@ module Sisimai::Lhost
|
|
|
76
72
|
end
|
|
77
73
|
end
|
|
78
74
|
return nil if recipients == 0
|
|
79
|
-
|
|
80
|
-
dscontents.each do |e|
|
|
81
|
-
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
|
82
|
-
|
|
83
|
-
MessagesOf.each_key do |r|
|
|
84
|
-
# Verify each regular expression of session errors
|
|
85
|
-
next if MessagesOf[r].none? { |a| e['diagnosis'].include?(a) }
|
|
86
|
-
e['reason'] = r
|
|
87
|
-
break
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
75
|
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
|
92
76
|
end
|
|
93
77
|
def description; return 'BIGLOBE: https://www.biglobe.ne.jp'; end
|
|
@@ -72,7 +72,8 @@ module Sisimai::Lhost
|
|
|
72
72
|
next unless o = Sisimai::RFC1894.field(e)
|
|
73
73
|
v = dscontents[-1]
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
case o[3]
|
|
76
|
+
when "addr"
|
|
76
77
|
# Final-Recipient: rfc822; kijitora@example.jp
|
|
77
78
|
# X-Actual-Recipient: rfc822; kijitora@example.co.jp
|
|
78
79
|
if o[0] == 'final-recipient'
|
|
@@ -88,7 +89,7 @@ module Sisimai::Lhost
|
|
|
88
89
|
# X-Actual-Recipient: rfc822; kijitora@example.co.jp
|
|
89
90
|
v['alias'] = o[2]
|
|
90
91
|
end
|
|
91
|
-
|
|
92
|
+
when "code"
|
|
92
93
|
# Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
|
|
93
94
|
v['spec'] = o[1]
|
|
94
95
|
v['diagnosis'] = o[2]
|
|
@@ -116,7 +117,7 @@ module Sisimai::Lhost
|
|
|
116
117
|
# Continued line of the value of Diagnostic-Code field
|
|
117
118
|
next if readslices[-2].start_with?('Diagnostic-Code:') == false
|
|
118
119
|
next if e.start_with?(' ') == false
|
|
119
|
-
v['diagnosis'] += "
|
|
120
|
+
v['diagnosis'] += " " + e
|
|
120
121
|
readslices[-1] = "Diagnostic-Code: #{e}"
|
|
121
122
|
end
|
|
122
123
|
end
|
|
@@ -127,7 +128,7 @@ module Sisimai::Lhost
|
|
|
127
128
|
# Set default values if each value is empty.
|
|
128
129
|
permessage.each_key { |a| e[a] ||= permessage[a] || '' }
|
|
129
130
|
e['command'] = thecommand if e["command"].empty?
|
|
130
|
-
e['diagnosis'] =
|
|
131
|
+
e['diagnosis'] = e['diagnosis'] || ''
|
|
131
132
|
|
|
132
133
|
MessagesOf.each_key do |r|
|
|
133
134
|
# Verify each regular expression of session errors
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
module Sisimai::Lhost
|
|
2
|
+
# Sisimai::Lhost::DeutscheTelekom decodes a bounce email which created by Deutsche Telekom or
|
|
3
|
+
# T-Online. Methods in the module are called from only Sisimai::Message.
|
|
4
|
+
module DeutscheTelekom
|
|
5
|
+
class << self
|
|
6
|
+
require 'sisimai/lhost'
|
|
7
|
+
|
|
8
|
+
Indicators = Sisimai::Lhost.INDICATORS
|
|
9
|
+
BannerDTAG = Sisimai::Lhost.BannerDTAG
|
|
10
|
+
StartingOf = { message: [BannerDTAG[1]] }.freeze
|
|
11
|
+
|
|
12
|
+
# @abstract Decodes the bounce message from DeutscheTelekom
|
|
13
|
+
# @param [Hash] mhead Message headers of a bounce email
|
|
14
|
+
# @param [String] mbody Message body of a bounce email
|
|
15
|
+
# @return [Hash] Bounce data list and message/rfc822 part
|
|
16
|
+
# @return [Nil] it failed to decode or the arguments are missing
|
|
17
|
+
def inquire(mhead, mbody)
|
|
18
|
+
# - T-Online: https://www.t-online.de/, @t-online.de, @magenta.de
|
|
19
|
+
# - DeutscheTelekom: https://www.telekom.com/
|
|
20
|
+
# - Based on the bounce format of Smail 3, the original design model for Exim
|
|
21
|
+
# - Tailored for Deutsche Telekom's internal Smail 3 fork with custom banners
|
|
22
|
+
# - Module name follows the infrastructure owner for cross-language compatibility
|
|
23
|
+
# - Smail 3: http://www.weird.com/~woods/projects/smail.html
|
|
24
|
+
return nil unless BannerDTAG.any? { |a| mbody.include?(a) }
|
|
25
|
+
|
|
26
|
+
# smail-3.2.0.108/src/
|
|
27
|
+
# notify.c:1052|(void) fprintf(f, "Subject: mail failed, %s\nReference: <%s@%s>\n\n",
|
|
28
|
+
# notify.c:1053| subject_to, message_id, primary_name);
|
|
29
|
+
#
|
|
30
|
+
# T-Online specific headers
|
|
31
|
+
# Received: from mailin42.aul.t-online.de (mailin42.aul.t-online.de [192.51.100.1])
|
|
32
|
+
# by mailout11.t-online.de (Postfix) with SMTP id 05E5A1CAC0
|
|
33
|
+
# From: Mail Delivery System <Mailer-Daemon@t-online.de>
|
|
34
|
+
# X-TOI-MSGID: c9412855-531f-497b-b007-5ffc033877a0
|
|
35
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = nil
|
|
36
|
+
emailparts = Sisimai::RFC5322.part(mbody, [BannerDTAG[3], BannerDTAG[2]])
|
|
37
|
+
bodyslices = emailparts[0].split("\n")
|
|
38
|
+
messagelog = ''
|
|
39
|
+
readcursor = 0 # (Integer) Points the current cursor position
|
|
40
|
+
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
|
41
|
+
|
|
42
|
+
while e = bodyslices.shift do
|
|
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.
|
|
45
|
+
if readcursor == 0
|
|
46
|
+
# Beginning of the bounce message or delivery status part
|
|
47
|
+
if e.start_with?(StartingOf[:message][0])
|
|
48
|
+
# |------------------------- Failed addresses follow: ---------------------|
|
|
49
|
+
readcursor |= Indicators[:deliverystatus]
|
|
50
|
+
else
|
|
51
|
+
# |------------------------- Message log follows: -------------------------|
|
|
52
|
+
# The line above may appears only in Smail 3.
|
|
53
|
+
#
|
|
54
|
+
# smail-3.2.0.108/src/
|
|
55
|
+
# models.c:787| if (deliver == NULL && defer == NULL) {
|
|
56
|
+
# models.c:788| write_log(WRITE_LOG_MLOG, "no valid recipients were found for this message");
|
|
57
|
+
# models.c:789| return_to_sender = TRUE;
|
|
58
|
+
# models.c:879| }
|
|
59
|
+
messagelog += ' ' + e if e != "" && e.include?(BannerDTAG[0]) == false
|
|
60
|
+
end
|
|
61
|
+
next
|
|
62
|
+
end
|
|
63
|
+
next if (readcursor & Indicators[:deliverystatus]) == 0 || e.empty?
|
|
64
|
+
|
|
65
|
+
# |------------------------- Failed addresses follow: ---------------------|
|
|
66
|
+
# <example@t-online.de>
|
|
67
|
+
# 552 5.2.2 <example@t-online.de> Quota exceeded (mailbox for user is full)
|
|
68
|
+
#
|
|
69
|
+
# |------------------------- Message header follows: ----------------------|
|
|
70
|
+
# Received: from mail.fragdenstaat.de ([94.130.55.89]) by mailin41.mgt.mul.t-online.de.example.com
|
|
71
|
+
# with (TLSv1.3:TLS_AES_256_GCM_SHA384 encrypted)
|
|
72
|
+
# ...
|
|
73
|
+
v = dscontents[-1]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if e.start_with?(' <') && e.end_with?('>') && e.count(' ') == 1
|
|
77
|
+
# Deutsche Telekom: The recipient address is enclosed in angle brackets.
|
|
78
|
+
# |------------------------- Failed addresses follow: ---------------------|
|
|
79
|
+
if v["recipient"] != ""
|
|
80
|
+
# There are multiple recipient addresses in the message body.
|
|
81
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
|
82
|
+
v = dscontents[-1]
|
|
83
|
+
end
|
|
84
|
+
v['recipient'] = e[2, e.size]
|
|
85
|
+
recipients += 1
|
|
86
|
+
|
|
87
|
+
elsif Sisimai::String.aligned(e, [' ', '@', '.', ' ... '])
|
|
88
|
+
# Smail 3:
|
|
89
|
+
# - The recipient address is not enclosed in angle brackets.
|
|
90
|
+
# - Error message begins with " ... failed:"
|
|
91
|
+
# smail-3.2.0.108/src/
|
|
92
|
+
# notify.c:845| if (cur->error) {
|
|
93
|
+
# notify.c:846| (void) fprintf(f, " %s ... failed: %s\n",
|
|
94
|
+
# notify.c:847| cur->in_addr ? cur->in_addr : "(unknown)",
|
|
95
|
+
# notify.c:848| cur->error->message);
|
|
96
|
+
# notify.c:849| }
|
|
97
|
+
# |------------------------- Failed addresses follow: ---------------------|
|
|
98
|
+
# kijitora@neko.nyaan.example.com ... unknown host
|
|
99
|
+
if v["recipient"] != ""
|
|
100
|
+
# There are multiple recipient addresses in the message body.
|
|
101
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
|
102
|
+
v = dscontents[-1]
|
|
103
|
+
end
|
|
104
|
+
v['recipient'] = e[1, e.index(' ... ')]
|
|
105
|
+
v['diagnosis'] = messagelog << ' ' + e
|
|
106
|
+
recipients += 1
|
|
107
|
+
|
|
108
|
+
else
|
|
109
|
+
# 552 5.2.2 <example@t-online.de> Quota exceeded (mailbox for user is full)
|
|
110
|
+
v['diagnosis'] = e
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
return nil if recipients == 0
|
|
114
|
+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
|
115
|
+
end
|
|
116
|
+
def description; return 'DeutscheTelekom'; end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
data/lib/sisimai/lhost/domino.rb
CHANGED
|
@@ -9,8 +9,6 @@ module Sisimai::Lhost
|
|
|
9
9
|
Boundaries = ['Content-Type: message/rfc822'].freeze
|
|
10
10
|
StartingOf = {message: ['Your message']}.freeze
|
|
11
11
|
MessagesOf = {
|
|
12
|
-
"filtered" => ["Cannot route mail to user"],
|
|
13
|
-
"systemerror" => ["Several matches found in Domino Directory"],
|
|
14
12
|
"userunknown" => [
|
|
15
13
|
"not listed in Domino Directory",
|
|
16
14
|
"not listed in public Name & Address Book",
|
|
@@ -120,7 +118,6 @@ module Sisimai::Lhost
|
|
|
120
118
|
return nil if recipients == 0
|
|
121
119
|
|
|
122
120
|
dscontents.each do |e|
|
|
123
|
-
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
|
124
121
|
e['recipient'] = Sisimai::Address.s3s4(e['recipient'])
|
|
125
122
|
permessage.each_key { |a| e[a] ||= permessage[a] || '' }
|
|
126
123
|
|
|
@@ -14,23 +14,6 @@ module Sisimai::Lhost
|
|
|
14
14
|
# https://github.com/corecode/dma/blob/ffad280aa40c242aa9a2cb9ca5b1b6e8efedd17e/mail.c#L84
|
|
15
15
|
message: ['This is the DragonFly Mail Agent '],
|
|
16
16
|
}.freeze
|
|
17
|
-
MessagesOf = {
|
|
18
|
-
'expired' => [
|
|
19
|
-
# https://github.com/corecode/dma/blob/master/dma.c#L370C1-L374C19
|
|
20
|
-
# dma.c:370| if (gettimeofday(&now, NULL) == 0 &&
|
|
21
|
-
# dma.c:371| (now.tv_sec - st.st_mtim.tv_sec > MAX_TIMEOUT)) {
|
|
22
|
-
# dma.c:372| snprintf(errmsg, sizeof(errmsg),
|
|
23
|
-
# dma.c:373| "Could not deliver for the last %d seconds. Giving up.",
|
|
24
|
-
# dma.c:374| MAX_TIMEOUT);
|
|
25
|
-
# dma.c:375| goto bounce;
|
|
26
|
-
# dma.c:376| }
|
|
27
|
-
'Could not deliver for the last ',
|
|
28
|
-
],
|
|
29
|
-
'hostunknown' => [
|
|
30
|
-
# net.c:663| snprintf(errmsg, sizeof(errmsg), "DNS lookup failure: host %s not found", host);
|
|
31
|
-
'DNS lookup failure: host ',
|
|
32
|
-
],
|
|
33
|
-
}.freeze
|
|
34
17
|
|
|
35
18
|
# @abstract Decodes the bounce message from DMA: DragonFly Mail Agent
|
|
36
19
|
# @param [Hash] mhead Message headers of a bounce email
|
|
@@ -92,16 +75,6 @@ module Sisimai::Lhost
|
|
|
92
75
|
end
|
|
93
76
|
end
|
|
94
77
|
return nil if recipients == 0
|
|
95
|
-
|
|
96
|
-
dscontents.each do |e|
|
|
97
|
-
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
|
98
|
-
MessagesOf.each_key do |r|
|
|
99
|
-
# Verify each regular expression of session errors
|
|
100
|
-
next if MessagesOf[r].none? { |a| e['diagnosis'].include?(a) }
|
|
101
|
-
e['reason'] = r
|
|
102
|
-
break
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
78
|
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
|
106
79
|
end
|
|
107
80
|
def description; return 'DragonFly'; end
|
|
@@ -11,7 +11,6 @@ module Sisimai::Lhost
|
|
|
11
11
|
message: ['This message was created automatically by mail delivery software'],
|
|
12
12
|
error: ['For the following reason:'],
|
|
13
13
|
}.freeze
|
|
14
|
-
MessagesOf = {'mesgtoobig' => ['Mail size limit exceeded']}.freeze
|
|
15
14
|
|
|
16
15
|
# @abstract Decode the bounce message from 1&1
|
|
17
16
|
# @param [Hash] mhead Message headers of a bounce email
|
|
@@ -90,7 +89,7 @@ module Sisimai::Lhost
|
|
|
90
89
|
p1 = e['diagnosis'].index('host: ')
|
|
91
90
|
p2 = e['diagnosis'].index(' reason:')
|
|
92
91
|
|
|
93
|
-
e['rhost'] =
|
|
92
|
+
e['rhost'] = e['diagnosis'][p1 + 6, p2 - p1 - 6]
|
|
94
93
|
e['command'] = 'DATA' if e['diagnosis'].include?('for TEXT command')
|
|
95
94
|
e['spec'] = 'SMTP' if e['diagnosis'].include?('SMTP error')
|
|
96
95
|
e['status'] = Sisimai::SMTP::Status.find(e['diagnosis'])
|
|
@@ -98,14 +97,6 @@ module Sisimai::Lhost
|
|
|
98
97
|
# For the following reason:
|
|
99
98
|
e['diagnosis'][0, StartingOf[:error][0].size] = ''
|
|
100
99
|
end
|
|
101
|
-
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
|
102
|
-
|
|
103
|
-
MessagesOf.each_key do |r|
|
|
104
|
-
# Verify each regular expression of session errors
|
|
105
|
-
next if MessagesOf[r].none? { |a| e['diagnosis'].include?(a) }
|
|
106
|
-
e['reason'] = r
|
|
107
|
-
break
|
|
108
|
-
end
|
|
109
100
|
end
|
|
110
101
|
|
|
111
102
|
return {"ds" => dscontents, "rfc822" => emailparts[1]}
|
|
@@ -158,19 +158,20 @@ module Sisimai::Lhost
|
|
|
158
158
|
# Subject: ...
|
|
159
159
|
# Sent: Thu, 29 Apr 2010 18:14:35 +0000
|
|
160
160
|
#
|
|
161
|
-
|
|
161
|
+
case
|
|
162
|
+
when e.start_with?(' To: ', ' To: ')
|
|
162
163
|
# To: shironeko@example.jp
|
|
163
164
|
next if connheader['to'].empty? == false
|
|
164
165
|
connheader['to'] = e[e.rindex(' ') + 1, e.size]
|
|
165
166
|
connvalues += 1
|
|
166
167
|
|
|
167
|
-
|
|
168
|
+
when e.start_with?(' Subject: ', ' Subject: ')
|
|
168
169
|
# Subject: ...
|
|
169
170
|
next if connheader['subject'].empty? == false
|
|
170
171
|
connheader['subject'] = e[e.rindex(' ') + 1, e.size]
|
|
171
172
|
connvalues += 1
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
when e.start_with?(' Sent: ', ' Sent: ')
|
|
174
175
|
# Sent: Thu, 29 Apr 2010 18:14:35 +0000
|
|
175
176
|
# Sent: 4/29/99 9:19:59 AM
|
|
176
177
|
next if connheader['date'].empty? == false
|
|
@@ -207,7 +208,6 @@ module Sisimai::Lhost
|
|
|
207
208
|
|
|
208
209
|
# Copy alternative error message
|
|
209
210
|
e['diagnosis'] = "#{e['alterrors']} #{e['diagnosis']}"
|
|
210
|
-
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
|
211
211
|
e.delete('alterrors')
|
|
212
212
|
end
|
|
213
213
|
end
|
|
@@ -38,12 +38,12 @@ module Sisimai::Lhost
|
|
|
38
38
|
"RESOLVER.ADR.RecipNotFound" => "userunknown", # 550 5.1.1 RESOLVER.ADR.RecipNotFound
|
|
39
39
|
"RESOLVER.ADR.RecipientNotFound" => "userunknown", # 550 5.1.1 RESOLVER.ADR.RecipientNotFound
|
|
40
40
|
"RESOLVER.ADR.ExRecipNotFound" => "userunknown", # 550 5.1.1 RESOLVER.ADR.ExRecipNotFound
|
|
41
|
-
"RESOLVER.ADR.RecipLimit" => "
|
|
41
|
+
"RESOLVER.ADR.RecipLimit" => "ratelimited", # 550 5.5.3 RESOLVER.ADR.RecipLimit
|
|
42
42
|
"RESOLVER.ADR.InvalidInSmtp" => "systemerror", # 550 5.1.0 RESOLVER.ADR.InvalidInSmtp
|
|
43
43
|
"RESOLVER.ADR.Ambiguous" => "systemerror", # 550 5.1.4 RESOLVER.ADR.Ambiguous, 420 4.2.0 RESOLVER.ADR.Ambiguous
|
|
44
44
|
"RESOLVER.RST.AuthRequired" => "securityerror", # 550 5.7.1 RESOLVER.RST.AuthRequired
|
|
45
45
|
"RESOLVER.RST.NotAuthorized" => "rejected", # 550 5.7.1 RESOLVER.RST.NotAuthorized
|
|
46
|
-
"RESOLVER.RST.RecipSizeLimit" => "
|
|
46
|
+
"RESOLVER.RST.RecipSizeLimit" => "emailtoolarge", # 550 5.2.3 RESOLVER.RST.RecipSizeLimit
|
|
47
47
|
"QUEUE.Expired" => "expired", # 550 4.4.7 QUEUE.Expired
|
|
48
48
|
}.freeze
|
|
49
49
|
MailSender = ["postmaster@outlook.com", ".onmicrosoft.com"].freeze
|
|
@@ -64,7 +64,9 @@ module Sisimai::Lhost
|
|
|
64
64
|
def inquire(mhead, mbody)
|
|
65
65
|
proceedsto = 0
|
|
66
66
|
proceedsto += 1 if EmailTitle.any? { |a| mhead["subject"].include?(a) }
|
|
67
|
-
proceedsto += 1 if MailSender.any? { |a| mhead["from"].include?(a)
|
|
67
|
+
proceedsto += 1 if MailSender.any? { |a| mhead["from"].include?(a) }
|
|
68
|
+
proceedsto += 1 if StartingOf[:error].any? { |a| mbody.include?(a) }
|
|
69
|
+
proceedsto += 1 if StartingOf[:message].any? { |a| mbody.include?(a) }
|
|
68
70
|
proceedsto += 1 if mhead["content-language"]
|
|
69
71
|
return nil if proceedsto < 2
|
|
70
72
|
|
|
@@ -150,9 +152,6 @@ module Sisimai::Lhost
|
|
|
150
152
|
return nil if recipients == 0
|
|
151
153
|
|
|
152
154
|
dscontents.each do |e|
|
|
153
|
-
# Tidy up the error message in $e->{'diagnosis'}, Try to detect the bounce reason.
|
|
154
|
-
e["diagnosis"] = Sisimai::String.sweep(e["diagnosis"])
|
|
155
|
-
|
|
156
155
|
p0 = -1; StartingOf[:error].each do |r|
|
|
157
156
|
# Try to find the NDR subject string such as "RESOLVER.ADR.RecipientNotFound" from the
|
|
158
157
|
# error message
|
data/lib/sisimai/lhost/exim.rb
CHANGED
|
@@ -58,32 +58,9 @@ module Sisimai::Lhost
|
|
|
58
58
|
],
|
|
59
59
|
}.freeze
|
|
60
60
|
MessagesOf = {
|
|
61
|
-
# find exim/ -type f -exec grep 'message = US' {} /dev/null \;
|
|
62
|
-
# route.c:1158| DEBUG(D_uid) debug_printf("getpwnam() returned NULL (user not found)\n");
|
|
63
61
|
# find exim/ -type f -exec grep 'message = US' {} /dev/null \;
|
|
64
62
|
# route.c:1158| DEBUG(D_uid) debug_printf("getpwnam() returned NULL (user not found)\n");
|
|
65
63
|
"userunknown" => ["user not found"],
|
|
66
|
-
# transports/smtp.c:3524| addr->message = US"all host address lookups failed permanently";
|
|
67
|
-
# routers/dnslookup.c:331| addr->message = US"all relevant MX records point to non-existent hosts";
|
|
68
|
-
# route.c:1826| uschar *message = US"Unrouteable address";
|
|
69
|
-
"hostunknown" => [
|
|
70
|
-
"all host address lookups failed permanently",
|
|
71
|
-
"all relevant MX records point to non-existent hosts",
|
|
72
|
-
"Unrouteable address",
|
|
73
|
-
],
|
|
74
|
-
# transports/appendfile.c:2567| addr->user_message = US"mailbox is full";
|
|
75
|
-
# transports/appendfile.c:3049| addr->message = string_sprintf("mailbox is full "
|
|
76
|
-
# transports/appendfile.c:3050| "(quota exceeded while writing to file %s)", filename);
|
|
77
|
-
"mailboxfull" => [
|
|
78
|
-
"mailbox is full",
|
|
79
|
-
"error: quota exceed",
|
|
80
|
-
],
|
|
81
|
-
# routers/dnslookup.c:328| addr->message = US"an MX or SRV record indicated no SMTP service";
|
|
82
|
-
# transports/smtp.c:3502| addr->message = US"no host found for existing SMTP connection";
|
|
83
|
-
"notaccept" => [
|
|
84
|
-
"an MX or SRV record indicated no SMTP service",
|
|
85
|
-
"no host found for existing SMTP connection",
|
|
86
|
-
],
|
|
87
64
|
# parser.c:666| *errorptr = string_sprintf("%s (expected word or \"<\")", *errorptr);
|
|
88
65
|
# parser.c:701| if(bracket_count++ > 5) FAILED(US"angle-brackets nested too deep");
|
|
89
66
|
# parser.c:738| FAILED(US"domain missing in source-routed address");
|
|
@@ -94,35 +71,14 @@ module Sisimai::Lhost
|
|
|
94
71
|
"domain missing in source-routed address",
|
|
95
72
|
"malformed address:",
|
|
96
73
|
],
|
|
97
|
-
# deliver.c:5614| addr->message = US"delivery to file forbidden";
|
|
98
|
-
# deliver.c:5624| addr->message = US"delivery to pipe forbidden";
|
|
99
|
-
# transports/pipe.c:1156| addr->user_message = US"local delivery failed";
|
|
100
|
-
"systemerror" => [
|
|
101
|
-
"delivery to file forbidden",
|
|
102
|
-
"delivery to pipe forbidden",
|
|
103
|
-
"local delivery failed",
|
|
104
|
-
"LMTP error after ",
|
|
105
|
-
],
|
|
106
|
-
# deliver.c:5425| new->message = US"Too many \"Received\" headers - suspected mail loop";
|
|
107
|
-
"contenterror" => ['Too many "Received" headers'],
|
|
108
74
|
}.freeze
|
|
109
75
|
DelayedFor = [
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
# smtp.c:3508| US"all hosts have been failing for a long time and were last tried "
|
|
114
|
-
# "after this message arrived";
|
|
115
|
-
# deliver.c:7459| print_address_error(addr, f, US"Delay reason: ");
|
|
116
|
-
# deliver.c:7586| "Message %s has been frozen%s.\nThe sender is <%s>.\n", message_id,
|
|
117
|
-
# receive.c:4021| moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
|
|
118
|
-
# receive.c:4022| "Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
|
|
119
|
-
"retry timeout exceeded",
|
|
76
|
+
# deliver.c:7475| "No action is required on your part. Delivery attempts will continue for\n"
|
|
77
|
+
# smtp.c:3508| US"retry time not reached for any host after a long failure period" :
|
|
78
|
+
# deliver.c:7459| print_address_error(addr, f, US"Delay reason: ");
|
|
120
79
|
"No action is required on your part",
|
|
121
80
|
"retry time not reached for any host after a long failure period",
|
|
122
|
-
"all hosts have been failing for a long time and were last tried",
|
|
123
81
|
"Delay reason: ",
|
|
124
|
-
"has been frozen",
|
|
125
|
-
"was frozen on arrival by ",
|
|
126
82
|
].freeze
|
|
127
83
|
|
|
128
84
|
# @abstract Decodes the bounce message from Exim
|
|
@@ -133,6 +89,8 @@ module Sisimai::Lhost
|
|
|
133
89
|
def inquire(mhead, mbody)
|
|
134
90
|
# Message-Id: <E1P1YNN-0003AD-Ga@example.org>
|
|
135
91
|
# X-Failed-Recipients: kijitora@example.ed.jp
|
|
92
|
+
return nil if Sisimai::Lhost.BannerDTAG.any? { |a| mbody.include?(a) }
|
|
93
|
+
|
|
136
94
|
thirdparty = false
|
|
137
95
|
proceedsto = 0
|
|
138
96
|
messageidv = mhead["message-id"] || ""
|
|
@@ -164,9 +122,11 @@ module Sisimai::Lhost
|
|
|
164
122
|
end
|
|
165
123
|
return nil if proceedsto < 2 && thirdparty == false
|
|
166
124
|
|
|
125
|
+
require "sisimai/reason"
|
|
167
126
|
require "sisimai/address"
|
|
168
127
|
require "sisimai/rfc2045"
|
|
169
128
|
require "sisimai/smtp/command"
|
|
129
|
+
require "sisimai/smtp/failure"
|
|
170
130
|
|
|
171
131
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = nil
|
|
172
132
|
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
|
@@ -253,7 +213,7 @@ module Sisimai::Lhost
|
|
|
253
213
|
# parser.c:749| goto PARSE_FAILED;
|
|
254
214
|
# parser.c:750| }
|
|
255
215
|
cv = Sisimai::Address.s3s4(e[p1, p2 - p1 - 1])
|
|
256
|
-
v["diagnosis"] =
|
|
216
|
+
v["diagnosis"] = e[p2 + 1, e.size]
|
|
257
217
|
else
|
|
258
218
|
# There is an email address only in the line
|
|
259
219
|
# kijitora@example.jp
|
|
@@ -286,7 +246,8 @@ module Sisimai::Lhost
|
|
|
286
246
|
# "e" matched with any field defined in RFC3464
|
|
287
247
|
next unless o = Sisimai::RFC1894.field(e)
|
|
288
248
|
|
|
289
|
-
|
|
249
|
+
case o[3]
|
|
250
|
+
when "addr"
|
|
290
251
|
# Final-Recipient: rfc822; kijitora@example.jp
|
|
291
252
|
# X-Actual-Recipient: rfc822; kijitora@example.co.jp
|
|
292
253
|
next if o[0] != "final-recipient"
|
|
@@ -294,7 +255,7 @@ module Sisimai::Lhost
|
|
|
294
255
|
v["spec"] = o[2].include?('@') ? "SMTP" : "X-UNIX"
|
|
295
256
|
end
|
|
296
257
|
|
|
297
|
-
|
|
258
|
+
when "code"
|
|
298
259
|
# Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
|
|
299
260
|
v["spec"] = o[1].upcase
|
|
300
261
|
v["diagnosis"] = o[2]
|
|
@@ -339,24 +300,22 @@ module Sisimai::Lhost
|
|
|
339
300
|
q["recipient"] = q["alias"]
|
|
340
301
|
end
|
|
341
302
|
end
|
|
342
|
-
|
|
303
|
+
elsif mhead["x-failed-recipients"]
|
|
343
304
|
# Fallback for getting recipient addresses
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
recipients = rcptinhead.size
|
|
305
|
+
# X-Failed-Recipients: kijitora@example.jp
|
|
306
|
+
rcptinhead = mhead["x-failed-recipients"].split(",")
|
|
307
|
+
rcptinhead.each do |e|
|
|
308
|
+
# Remove space characters
|
|
309
|
+
e.lstrip!
|
|
310
|
+
e.rstrip!
|
|
311
|
+
end
|
|
312
|
+
recipients = rcptinhead.size
|
|
353
313
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
end
|
|
314
|
+
while e = rcptinhead.shift do
|
|
315
|
+
# Insert each recipient address into dscontents
|
|
316
|
+
dscontents[-1]["recipient"] = e
|
|
317
|
+
next if dscontents.size == recipients
|
|
318
|
+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
|
360
319
|
end
|
|
361
320
|
end
|
|
362
321
|
return nil if recipients == 0
|
|
@@ -404,7 +363,7 @@ module Sisimai::Lhost
|
|
|
404
363
|
end
|
|
405
364
|
e.delete("alterrors")
|
|
406
365
|
end
|
|
407
|
-
|
|
366
|
+
p1 = e["diagnosis"].index("__") || -1
|
|
408
367
|
e["diagnosis"] = e["diagnosis"][0, p1] if p1 > 1
|
|
409
368
|
|
|
410
369
|
if e["rhost"].empty?
|
|
@@ -427,14 +386,11 @@ module Sisimai::Lhost
|
|
|
427
386
|
end
|
|
428
387
|
|
|
429
388
|
# Detect the reason of bounce
|
|
430
|
-
|
|
431
|
-
# HELO | Connected to 192.0.2.135 but my name was rejected.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
# MAIL | Connected to 192.0.2.135 but sender was rejected.
|
|
436
|
-
e["reason"] = "onhold"
|
|
437
|
-
|
|
389
|
+
case e["command"]
|
|
390
|
+
# - HELO | Connected to 192.0.2.135 but my name was rejected.
|
|
391
|
+
# - MAIL | Connected to 192.0.2.135 but sender was rejected.
|
|
392
|
+
when "HELO", "EHLO" then e["reason"] = "blocked"
|
|
393
|
+
when "MAIL" then e["reason"] = "onhold"
|
|
438
394
|
else
|
|
439
395
|
# Try to match the error message with each message pattern
|
|
440
396
|
MessagesOf.each_key do |r|
|
|
@@ -445,9 +401,7 @@ module Sisimai::Lhost
|
|
|
445
401
|
end
|
|
446
402
|
|
|
447
403
|
if e["reason"].empty?
|
|
448
|
-
# The reason "expired", or "mailererror"
|
|
449
404
|
e["reason"] = "expired" if DelayedFor.any? { |a| e["diagnosis"].include?(a) }
|
|
450
|
-
e["reason"] = "mailererror" if e["reason"].empty? && e["diagnosis"].include?("pipe to |")
|
|
451
405
|
end
|
|
452
406
|
end
|
|
453
407
|
end
|
|
@@ -461,21 +415,17 @@ module Sisimai::Lhost
|
|
|
461
415
|
#
|
|
462
416
|
# The value of "Status:" indicates permanent error but the value of SMTP reply code in
|
|
463
417
|
# Diagnostic-Code: field is "TEMPERROR"!!!!
|
|
464
|
-
re = e["reason"]
|
|
465
418
|
cr = Sisimai::SMTP::Reply.find(e["diagnosis"], e["status"])
|
|
466
419
|
cs = Sisimai::SMTP::Status.find(e["diagnosis"], cr)
|
|
420
|
+
re = e["reason"]
|
|
467
421
|
cv = ""
|
|
468
422
|
|
|
469
|
-
if cr
|
|
423
|
+
if Sisimai::SMTP::Failure.is_temporary(cr) || re == "expired"
|
|
470
424
|
# Set the pseudo status code as a temporary error
|
|
471
|
-
cv = Sisimai::SMTP::Status.code(re, true)
|
|
472
|
-
|
|
473
|
-
else
|
|
474
|
-
# Set the pseudo status code as a permanent error
|
|
475
|
-
cv = Sisimai::SMTP::Status.code(re, false)
|
|
425
|
+
cv = Sisimai::SMTP::Status.code(re, true) if Sisimai::Reason.is_explicit(re)
|
|
476
426
|
end
|
|
477
427
|
e["replycode"] = cr if e["replycode"].empty?
|
|
478
|
-
e["status"] = Sisimai::SMTP::Status.prefer(
|
|
428
|
+
e["status"] = Sisimai::SMTP::Status.prefer(cv, cs, cr) if e["status"].empty?
|
|
479
429
|
end
|
|
480
430
|
|
|
481
431
|
return { "ds" => dscontents, "rfc822" => emailparts[1] }
|