sisimai 5.5.0-java → 5.7.0-java

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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake-test.yml +0 -4
  3. data/ChangeLog.md +53 -0
  4. data/LICENSE +1 -1
  5. data/README-JA.md +26 -20
  6. data/README.md +30 -25
  7. data/lib/sisimai/address.rb +8 -31
  8. data/lib/sisimai/arf.rb +3 -5
  9. data/lib/sisimai/fact.rb +26 -36
  10. data/lib/sisimai/lhost/activehunter.rb +0 -2
  11. data/lib/sisimai/lhost/amazonses.rb +7 -9
  12. data/lib/sisimai/lhost/apachejames.rb +0 -1
  13. data/lib/sisimai/lhost/biglobe.rb +0 -16
  14. data/lib/sisimai/lhost/courier.rb +5 -4
  15. data/lib/sisimai/lhost/deutschetelekom.rb +120 -0
  16. data/lib/sisimai/lhost/domino.rb +0 -3
  17. data/lib/sisimai/lhost/dragonfly.rb +0 -27
  18. data/lib/sisimai/lhost/einsundeins.rb +1 -10
  19. data/lib/sisimai/lhost/exchange2003.rb +4 -4
  20. data/lib/sisimai/lhost/exchange2007.rb +5 -6
  21. data/lib/sisimai/lhost/exim.rb +35 -85
  22. data/lib/sisimai/lhost/ezweb.rb +12 -49
  23. data/lib/sisimai/lhost/fml.rb +4 -29
  24. data/lib/sisimai/lhost/gmail.rb +0 -23
  25. data/lib/sisimai/lhost/gmx.rb +7 -24
  26. data/lib/sisimai/lhost/googlegroups.rb +3 -3
  27. data/lib/sisimai/lhost/googleworkspace.rb +0 -4
  28. data/lib/sisimai/lhost/imailserver.rb +3 -9
  29. data/lib/sisimai/lhost/kddi.rb +6 -20
  30. data/lib/sisimai/lhost/mailfoundry.rb +0 -2
  31. data/lib/sisimai/lhost/mailmarshal.rb +1 -3
  32. data/lib/sisimai/lhost/messagingserver.rb +4 -15
  33. data/lib/sisimai/lhost/mfilter.rb +0 -1
  34. data/lib/sisimai/lhost/mimecast.rb +0 -1
  35. data/lib/sisimai/lhost/notes.rb +1 -2
  36. data/lib/sisimai/lhost/opensmtpd.rb +0 -40
  37. data/lib/sisimai/lhost/postfix.rb +10 -11
  38. data/lib/sisimai/lhost/qmail.rb +14 -81
  39. data/lib/sisimai/lhost/sendmail.rb +4 -4
  40. data/lib/sisimai/lhost/trendmicro.rb +3 -3
  41. data/lib/sisimai/lhost/v5sendmail.rb +0 -1
  42. data/lib/sisimai/lhost/verizon.rb +1 -2
  43. data/lib/sisimai/lhost/x1.rb +1 -2
  44. data/lib/sisimai/lhost/x2.rb +0 -2
  45. data/lib/sisimai/lhost/x3.rb +4 -9
  46. data/lib/sisimai/lhost/x6.rb +0 -1
  47. data/lib/sisimai/lhost/zoho.rb +0 -12
  48. data/lib/sisimai/lhost.rb +38 -19
  49. data/lib/sisimai/message.rb +3 -1
  50. data/lib/sisimai/order.rb +4 -1
  51. data/lib/sisimai/reason/authfailure.rb +9 -13
  52. data/lib/sisimai/reason/badreputation.rb +7 -7
  53. data/lib/sisimai/reason/blocked.rb +57 -83
  54. data/lib/sisimai/reason/contenterror.rb +16 -8
  55. data/lib/sisimai/reason/{mesgtoobig.rb → emailtoolarge.rb} +22 -25
  56. data/lib/sisimai/reason/expired.rb +27 -23
  57. data/lib/sisimai/reason/filtered.rb +13 -17
  58. data/lib/sisimai/reason/hasmoved.rb +2 -1
  59. data/lib/sisimai/reason/hostunknown.rb +25 -19
  60. data/lib/sisimai/reason/mailboxfull.rb +28 -49
  61. data/lib/sisimai/reason/networkerror.rb +28 -16
  62. data/lib/sisimai/reason/norelaying.rb +21 -20
  63. data/lib/sisimai/reason/notaccept.rb +13 -8
  64. data/lib/sisimai/reason/notcompliantrfc.rb +6 -9
  65. data/lib/sisimai/reason/policyviolation.rb +17 -26
  66. data/lib/sisimai/reason/ratelimited.rb +62 -0
  67. data/lib/sisimai/reason/rejected.rb +51 -59
  68. data/lib/sisimai/reason/requireptr.rb +13 -25
  69. data/lib/sisimai/reason/securityerror.rb +14 -19
  70. data/lib/sisimai/reason/spamdetected.rb +51 -101
  71. data/lib/sisimai/reason/suspend.rb +30 -24
  72. data/lib/sisimai/reason/syntaxerror.rb +1 -8
  73. data/lib/sisimai/reason/systemerror.rb +37 -23
  74. data/lib/sisimai/reason/systemfull.rb +1 -1
  75. data/lib/sisimai/reason/userunknown.rb +81 -112
  76. data/lib/sisimai/reason/virusdetected.rb +6 -8
  77. data/lib/sisimai/reason.rb +15 -15
  78. data/lib/sisimai/rfc1123.rb +1 -1
  79. data/lib/sisimai/rfc1894.rb +7 -6
  80. data/lib/sisimai/rfc2045.rb +2 -2
  81. data/lib/sisimai/rfc3464/thirdparty.rb +1 -1
  82. data/lib/sisimai/rfc3464.rb +10 -14
  83. data/lib/sisimai/rfc3834.rb +3 -4
  84. data/lib/sisimai/rfc791.rb +3 -38
  85. data/lib/sisimai/rhost/apple.rb +5 -5
  86. data/lib/sisimai/rhost/cloudflare.rb +2 -0
  87. data/lib/sisimai/rhost/cox.rb +22 -20
  88. data/lib/sisimai/rhost/facebook.rb +16 -16
  89. data/lib/sisimai/rhost/franceptt.rb +8 -3
  90. data/lib/sisimai/rhost/godaddy.rb +33 -15
  91. data/lib/sisimai/rhost/google.rb +63 -64
  92. data/lib/sisimai/rhost/iua.rb +1 -1
  93. data/lib/sisimai/rhost/messagelabs.rb +12 -12
  94. data/lib/sisimai/rhost/microsoft.rb +86 -86
  95. data/lib/sisimai/rhost/mimecast.rb +34 -34
  96. data/lib/sisimai/rhost/nttdocomo.rb +2 -2
  97. data/lib/sisimai/rhost/spectrum.rb +7 -7
  98. data/lib/sisimai/rhost/tencent.rb +9 -11
  99. data/lib/sisimai/rhost/yahooinc.rb +7 -8
  100. data/lib/sisimai/rhost.rb +1 -1
  101. data/lib/sisimai/smtp/command.rb +2 -0
  102. data/lib/sisimai/smtp/status.rb +73 -109
  103. data/lib/sisimai/string.rb +0 -27
  104. data/lib/sisimai/version.rb +1 -1
  105. data/set-of-emails/maildir/bsd/lhost-deutschetelekom-01.eml +66 -0
  106. data/set-of-emails/maildir/bsd/lhost-deutschetelekom-02.eml +68 -0
  107. data/set-of-emails/maildir/bsd/lhost-deutschetelekom-03.eml +50 -0
  108. data/set-of-emails/should-not-crash/p5-664-iomart-mail-filter.eml +258 -0
  109. data/set-of-emails/to-be-debugged-because/sisimai-cannot-parse-yet/.gitkeep +0 -0
  110. metadata +10 -6
  111. data/lib/sisimai/reason/exceedlimit.rb +0 -47
  112. data/lib/sisimai/reason/speeding.rb +0 -47
  113. 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" => "mesgtoobig",
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
- if whatnotify == "B"
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"] = Sisimai::String.sweep(e["diagnosticCode"])
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"] = Sisimai::String.sweep(p["smtpResponse"])
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
- if o[3] == 'addr'
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
- elsif o[3] == 'code'
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'] += " #{Sisimai::String.sweep(e)}"
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'] = Sisimai::String.sweep(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
+
@@ -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'] = Sisimai::String.sweep(e['diagnosis'][p1 + 6, p2 - p1 - 6])
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
- if e.start_with?(' To: ') || e.start_with?(' To: ')
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
- elsif e.start_with?(' Subject: ') || e.start_with?(' Subject: ')
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
- elsif e.start_with?(' Sent: ') || e.start_with?(' Sent: ')
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" => "toomanyconn", # 550 5.5.3 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" => "exceedlimit", # 550 5.2.3 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
@@ -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
- # retry.c:902| addr->message = (addr->message == NULL)? US"retry timeout exceeded" :
111
- # deliver.c:7475| "No action is required on your part. Delivery attempts will continue for\n"
112
- # smtp.c:3508| US"retry time not reached for any host after a long failure period" :
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"] = Sisimai::String.sweep(e[p2 + 1, e.size])
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
- if o[3] == "addr"
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
- elsif o[3] == "code"
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
- else
303
+ elsif mhead["x-failed-recipients"]
343
304
  # Fallback for getting recipient addresses
344
- if mhead["x-failed-recipients"]
345
- # X-Failed-Recipients: kijitora@example.jp
346
- rcptinhead = mhead["x-failed-recipients"].split(",")
347
- rcptinhead.each do |e|
348
- # Remove space characters
349
- e.lstrip!
350
- e.rstrip!
351
- end
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
- while e = rcptinhead.shift do
355
- # Insert each recipient address into dscontents
356
- dscontents[-1]["recipient"] = e
357
- next if dscontents.size == recipients
358
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
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
- e["diagnosis"] = Sisimai::String.sweep(e["diagnosis"]) || ""; p1 = e["diagnosis"].index("__") || -1
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
- if %w[HELO EHLO].index(e["command"])
431
- # HELO | Connected to 192.0.2.135 but my name was rejected.
432
- e["reason"] = "blocked"
433
-
434
- elsif e["command"] == "MAIL"
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[0,1] == "4" || re == "expired" || re == "mailboxfull"
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(cs, cv, cr) if e["status"].empty?
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] }