sisimai 4.22.3 → 4.22.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sisimai might be problematic. Click here for more details.

Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/Benchmarks.mk +54 -0
  4. data/ChangeLog.md +23 -2
  5. data/Developers.mk +42 -35
  6. data/Makefile +10 -0
  7. data/README-JA.md +13 -13
  8. data/README.md +14 -14
  9. data/lib/sisimai.rb +12 -18
  10. data/lib/sisimai/address.rb +64 -82
  11. data/lib/sisimai/arf.rb +27 -42
  12. data/lib/sisimai/bite/email.rb +2 -4
  13. data/lib/sisimai/bite/email/activehunter.rb +12 -17
  14. data/lib/sisimai/bite/email/amazonses.rb +30 -48
  15. data/lib/sisimai/bite/email/amazonworkmail.rb +20 -27
  16. data/lib/sisimai/bite/email/aol.rb +27 -35
  17. data/lib/sisimai/bite/email/apachejames.rb +17 -28
  18. data/lib/sisimai/bite/email/bigfoot.rb +20 -33
  19. data/lib/sisimai/bite/email/biglobe.rb +15 -24
  20. data/lib/sisimai/bite/email/courier.rb +37 -61
  21. data/lib/sisimai/bite/email/domino.rb +19 -28
  22. data/lib/sisimai/bite/email/einsundeins.rb +20 -34
  23. data/lib/sisimai/bite/email/exchange2003.rb +25 -43
  24. data/lib/sisimai/bite/email/exchange2007.rb +15 -23
  25. data/lib/sisimai/bite/email/exim.rb +101 -120
  26. data/lib/sisimai/bite/email/ezweb.rb +28 -44
  27. data/lib/sisimai/bite/email/facebook.rb +26 -37
  28. data/lib/sisimai/bite/email/fml.rb +11 -20
  29. data/lib/sisimai/bite/email/gmx.rb +17 -27
  30. data/lib/sisimai/bite/email/google.rb +19 -29
  31. data/lib/sisimai/bite/email/gsuite.rb +39 -48
  32. data/lib/sisimai/bite/email/imailserver.rb +25 -39
  33. data/lib/sisimai/bite/email/interscanmss.rb +19 -26
  34. data/lib/sisimai/bite/email/kddi.rb +20 -33
  35. data/lib/sisimai/bite/email/mailfoundry.rb +14 -24
  36. data/lib/sisimai/bite/email/mailmarshalsmtp.rb +15 -24
  37. data/lib/sisimai/bite/email/mailru.rb +40 -59
  38. data/lib/sisimai/bite/email/mcafee.rb +21 -35
  39. data/lib/sisimai/bite/email/messagelabs.rb +23 -38
  40. data/lib/sisimai/bite/email/messagingserver.rb +15 -27
  41. data/lib/sisimai/bite/email/mfilter.rb +19 -28
  42. data/lib/sisimai/bite/email/mxlogic.rb +31 -49
  43. data/lib/sisimai/bite/email/notes.rb +16 -24
  44. data/lib/sisimai/bite/email/office365.rb +29 -38
  45. data/lib/sisimai/bite/email/opensmtpd.rb +50 -67
  46. data/lib/sisimai/bite/email/outlook.rb +24 -36
  47. data/lib/sisimai/bite/email/postfix.rb +33 -42
  48. data/lib/sisimai/bite/email/qmail.rb +44 -59
  49. data/lib/sisimai/bite/email/receivingses.rb +28 -36
  50. data/lib/sisimai/bite/email/sendgrid.rb +28 -37
  51. data/lib/sisimai/bite/email/sendmail.rb +35 -51
  52. data/lib/sisimai/bite/email/surfcontrol.rb +17 -25
  53. data/lib/sisimai/bite/email/userdefined.rb +17 -28
  54. data/lib/sisimai/bite/email/v5sendmail.rb +32 -41
  55. data/lib/sisimai/bite/email/verizon.rb +31 -56
  56. data/lib/sisimai/bite/email/x1.rb +11 -18
  57. data/lib/sisimai/bite/email/x2.rb +11 -23
  58. data/lib/sisimai/bite/email/x3.rb +10 -19
  59. data/lib/sisimai/bite/email/x4.rb +46 -65
  60. data/lib/sisimai/bite/email/x5.rb +26 -37
  61. data/lib/sisimai/bite/email/yahoo.rb +11 -19
  62. data/lib/sisimai/bite/email/yandex.rb +19 -30
  63. data/lib/sisimai/bite/email/zoho.rb +21 -30
  64. data/lib/sisimai/bite/json.rb +1 -2
  65. data/lib/sisimai/bite/json/amazonses.rb +20 -25
  66. data/lib/sisimai/bite/json/sendgrid.rb +1 -1
  67. data/lib/sisimai/data.rb +36 -55
  68. data/lib/sisimai/data/json.rb +3 -3
  69. data/lib/sisimai/data/yaml.rb +1 -1
  70. data/lib/sisimai/datetime.rb +5 -21
  71. data/lib/sisimai/mail.rb +4 -6
  72. data/lib/sisimai/mail/maildir.rb +1 -1
  73. data/lib/sisimai/mda.rb +41 -44
  74. data/lib/sisimai/message.rb +2 -3
  75. data/lib/sisimai/message/email.rb +42 -52
  76. data/lib/sisimai/message/json.rb +7 -7
  77. data/lib/sisimai/mime.rb +25 -23
  78. data/lib/sisimai/order/email.rb +2 -2
  79. data/lib/sisimai/order/json.rb +2 -7
  80. data/lib/sisimai/reason.rb +41 -46
  81. data/lib/sisimai/reason/blocked.rb +60 -71
  82. data/lib/sisimai/reason/contenterror.rb +4 -8
  83. data/lib/sisimai/reason/delivered.rb +1 -3
  84. data/lib/sisimai/reason/exceedlimit.rb +10 -20
  85. data/lib/sisimai/reason/expired.rb +5 -9
  86. data/lib/sisimai/reason/feedback.rb +1 -3
  87. data/lib/sisimai/reason/filtered.rb +19 -38
  88. data/lib/sisimai/reason/hasmoved.rb +5 -8
  89. data/lib/sisimai/reason/hostunknown.rb +11 -18
  90. data/lib/sisimai/reason/mailboxfull.rb +14 -24
  91. data/lib/sisimai/reason/mailererror.rb +3 -5
  92. data/lib/sisimai/reason/mesgtoobig.rb +15 -25
  93. data/lib/sisimai/reason/networkerror.rb +8 -10
  94. data/lib/sisimai/reason/norelaying.rb +9 -14
  95. data/lib/sisimai/reason/notaccept.rb +9 -21
  96. data/lib/sisimai/reason/onhold.rb +3 -8
  97. data/lib/sisimai/reason/policyviolation.rb +8 -10
  98. data/lib/sisimai/reason/rejected.rb +36 -49
  99. data/lib/sisimai/reason/securityerror.rb +11 -13
  100. data/lib/sisimai/reason/spamdetected.rb +23 -37
  101. data/lib/sisimai/reason/suspend.rb +9 -10
  102. data/lib/sisimai/reason/syntaxerror.rb +3 -4
  103. data/lib/sisimai/reason/systemerror.rb +7 -9
  104. data/lib/sisimai/reason/systemfull.rb +2 -4
  105. data/lib/sisimai/reason/toomanyconn.rb +17 -30
  106. data/lib/sisimai/reason/undefined.rb +1 -3
  107. data/lib/sisimai/reason/userunknown.rb +28 -38
  108. data/lib/sisimai/reason/vacation.rb +4 -6
  109. data/lib/sisimai/reason/virusdetected.rb +4 -6
  110. data/lib/sisimai/rfc2606.rb +1 -2
  111. data/lib/sisimai/rfc3464.rb +87 -101
  112. data/lib/sisimai/rfc3834.rb +29 -39
  113. data/lib/sisimai/rfc5322.rb +17 -24
  114. data/lib/sisimai/rhost.rb +10 -7
  115. data/lib/sisimai/rhost/exchangeonline.rb +124 -255
  116. data/lib/sisimai/rhost/franceptt.rb +2 -2
  117. data/lib/sisimai/rhost/godaddy.rb +12 -25
  118. data/lib/sisimai/rhost/googleapps.rb +82 -183
  119. data/lib/sisimai/smtp.rb +4 -4
  120. data/lib/sisimai/smtp/error.rb +8 -8
  121. data/lib/sisimai/smtp/reply.rb +1 -1
  122. data/lib/sisimai/smtp/status.rb +1 -0
  123. data/lib/sisimai/string.rb +5 -7
  124. data/lib/sisimai/version.rb +1 -1
  125. data/set-of-emails/README.md +1 -1
  126. data/set-of-emails/maildir/bsd/README.md +50 -50
  127. data/sisimai-java.gemspec +1 -1
  128. data/sisimai.gemspec +1 -1
  129. metadata +5 -5
  130. data/lib/sisimai/skeleton.rb +0 -43
@@ -4,34 +4,31 @@ module Sisimai
4
4
  # Imported from p5-Sisimail/lib/Sisimai/RFC3834.pm
5
5
  class << self
6
6
  # http://tools.ietf.org/html/rfc3834
7
- Re0 = {
7
+ MarkingsOf = { :boundary => %r/\A__SISIMAI_PSEUDO_BOUNDARY__\z/ }
8
+ AutoReply1 = {
8
9
  # http://www.iana.org/assignments/auto-submitted-keywords/auto-submitted-keywords.xhtml
9
- :'auto-submitted' => %r/\Aauto-(?:generated|replied|notified)/i,
10
+ :'auto-submitted' => %r/\Aauto-(?:generated|replied|notified)/,
10
11
  # https://msdn.microsoft.com/en-us/library/ee219609(v=exchg.80).aspx
11
- :'x-auto-response-suppress' => %r/(?:OOF|AutoReply)/i,
12
+ :'x-auto-response-suppress' => %r/(?:oof|autoreply)/,
12
13
  :'precedence' => %r/\Aauto_reply\z/,
13
14
  :'subject' => %r/\A(?>
14
- Auto:
15
- |Auto[ ]Response:
16
- |Automatic[ ]reply:
17
- |Out[ ]of[ ](?:the[ ])*Office:
15
+ auto:
16
+ |auto[ ]response:
17
+ |automatic[ ]reply:
18
+ |out[ ]of[ ](?:the[ ])*office:
18
19
  )
19
- /xi,
20
+ /x,
20
21
  }.freeze
21
- Re1 = {
22
- :boundary => %r/\A__SISIMAI_PSEUDO_BOUNDARY__\z/,
23
- :endof => %r/\A__END_OF_EMAIL_MESSAGE__\z/
24
- }
25
- Re2 = {
22
+ Excludings = {
26
23
  :subject => %r/(?:
27
- SECURITY[ ]information[ ]for # sudo
28
- |Mail[ ]failure[ ][-] # Exim
24
+ security[ ]information[ ]for # sudo
25
+ |mail[ ]failure[ ][-] # Exim
29
26
  )
30
27
  /x,
31
- :from => %r/(?:root|postmaster|mailer-daemon)[@]/i,
28
+ :from => %r/(?:root|postmaster|mailer-daemon)[@]/,
32
29
  :to => %r/root[@]/,
33
30
  }.freeze
34
- ReV = %r{\A(?>
31
+ SubjectSet = %r{\A(?>
35
32
  (?:.+?)?Re:
36
33
  |Auto(?:[ ]Response):
37
34
  |Automatic[ ]reply:
@@ -42,14 +39,7 @@ module Sisimai
42
39
 
43
40
  def description; 'Detector for auto replied message'; end
44
41
  def smtpagent; 'RFC3834'; end
45
- def pattern; return Re0; end
46
- def headerlist
47
- return [
48
- 'Auto-Submitted',
49
- 'Precedence',
50
- 'X-Auto-Response-Suppress',
51
- ]
52
- end
42
+ def headerlist; return %w[Auto-Submitted Precedence X-Auto-Response-Suppress]; end
53
43
 
54
44
  # Detect auto reply message as RFC3834
55
45
  # @param [Hash] mhead Message header of a bounce email
@@ -72,22 +62,22 @@ module Sisimai
72
62
  match = 0
73
63
 
74
64
  # DETECT_EXCLUSION_MESSAGE
75
- Re2.each_key do |e|
65
+ Excludings.each_key do |e|
76
66
  # Exclude message from root@
77
67
  next unless mhead.key?(e.to_s)
78
68
  next unless mhead[e.to_s]
79
- next unless mhead[e.to_s] =~ Re2[e]
69
+ next unless mhead[e.to_s].downcase =~ Excludings[e]
80
70
  leave = 1
81
71
  break
82
72
  end
83
73
  return nil if leave > 0
84
74
 
85
75
  # DETECT_AUTO_REPLY_MESSAGE
86
- Re0.each_key do |e|
76
+ AutoReply1.each_key do |e|
87
77
  # RFC3834 Auto-Submitted and other headers
88
78
  next unless mhead.key?(e.to_s)
89
79
  next unless mhead[e.to_s]
90
- next unless mhead[e.to_s] =~ Re0[e]
80
+ next unless mhead[e.to_s].downcase =~ AutoReply1[e]
91
81
  match += 1
92
82
  break
93
83
  end
@@ -95,7 +85,6 @@ module Sisimai
95
85
 
96
86
  require 'sisimai/bite/email'
97
87
  require 'sisimai/address'
98
-
99
88
  dscontents = [Sisimai::Bite.DELIVERYSTATUS]
100
89
  hasdivided = mbody.scrub('?').split("\n")
101
90
  rfc822part = '' # (String) message/rfc822-headers part
@@ -107,7 +96,7 @@ module Sisimai
107
96
  v = dscontents[-1]
108
97
 
109
98
  # RECIPIENT_ADDRESS
110
- ['from', 'return-path'].each do |e|
99
+ %w[from return-path].each do |e|
111
100
  # Try to get the address of the recipient
112
101
  next unless mhead.key?(e)
113
102
  next unless mhead[e]
@@ -127,13 +116,13 @@ module Sisimai
127
116
  # the boundary string.
128
117
  require 'sisimai/mime'
129
118
  b0 = Sisimai::MIME.boundary(mhead['content-type'], 0)
130
- Re1[:boundary] = %r/\A\Q#{b0}\E\z/ unless b0.empty?
119
+ MarkingsOf[:boundary] = %r/\A\Q#{b0}\E\z/ unless b0.empty?
131
120
  end
132
121
 
133
122
  # BODY_PARSER: Get vacation message
134
- hasdivided.each do |e|
123
+ while e = hasdivided.shift do
135
124
  # Read the first 5 lines except a blank line
136
- countuntil += 1 if e =~ Re1[:boundary]
125
+ countuntil += 1 if e =~ MarkingsOf[:boundary]
137
126
 
138
127
  unless e.size > 0
139
128
  # Check a blank line
@@ -141,11 +130,12 @@ module Sisimai
141
130
  break if blanklines > countuntil
142
131
  next
143
132
  end
144
- next unless e =~ / /
145
- next if e =~ /\AContent-(?:Type|Transfer)/
133
+ next unless e.include?(' ')
134
+ next if e.start_with?('Content-Type')
135
+ next if e.start_with?('Content-Transfer')
146
136
 
147
137
  v['diagnosis'] ||= ''
148
- v['diagnosis'] += e + ' '
138
+ v['diagnosis'] << e + ' '
149
139
  haveloaded += 1
150
140
  break if haveloaded >= maxmsgline
151
141
  end
@@ -160,9 +150,9 @@ module Sisimai
160
150
 
161
151
  v.each_key { |a| v[a] ||= '' }
162
152
 
163
- if cv = mhead['subject'].match(ReV)
153
+ if cv = mhead['subject'].match(SubjectSet)
164
154
  # Get the Subject header from the original message
165
- rfc822part = sprintf("Subject: %s\n", cv[1])
155
+ rfc822part = 'Subject: ' << cv[1] + "\n"
166
156
  end
167
157
  return { 'ds' => dscontents, 'rfc822' => rfc822part }
168
158
  end
@@ -7,15 +7,11 @@ module Sisimai
7
7
  :messageid => ['Message-Id'],
8
8
  :subject => ['Subject'],
9
9
  :listid => ['List-Id'],
10
- :date => ['Date', 'Posted-Date', 'Posted', 'Resent-Date'],
11
- :addresser => [
12
- 'From', 'Return-Path', 'Reply-To', 'Errors-To', 'Reverse-Path',
13
- 'X-Postfix-Sender', 'Envelope-From', 'X-Envelope-From',
14
- ],
15
- :recipient => [
16
- 'To', 'Delivered-To', 'Forward-Path', 'Envelope-To',
17
- 'X-Envelope-To', 'Resent-To', 'Apparently-To'
18
- ],
10
+ :date => %w[Date Posted-Date Posted Resent-Date],
11
+ :addresser => %w[
12
+ From Return-Path Reply-To Errors-To Reverse-Path X-Postfix-Sender Envelope-From X-Envelope-From
13
+ ],
14
+ :recipient => %w[To Delivered-To Forward-Path Envelope-To X-Envelope-To Resent-To Apparently-To],
19
15
  }.freeze
20
16
 
21
17
  build_regular_expressions = lambda do
@@ -95,7 +91,7 @@ module Sisimai
95
91
  def is_domainpart(dpart)
96
92
  return false unless dpart.is_a?(::String)
97
93
  return false if dpart =~ /(?:[\x00-\x1f]|\x1f)/
98
- return false if dpart =~ /[@]/
94
+ return false if dpart.include?('@')
99
95
  return true if dpart =~ Re[:domain]
100
96
  return false
101
97
  end
@@ -113,8 +109,8 @@ module Sisimai
113
109
  |\A(?:mailer-daemon|postmaster)\z
114
110
  |[ ]?mailer-daemon[ ]
115
111
  )
116
- /xi
117
- return true if email =~ re
112
+ /x
113
+ return true if email.downcase =~ re
118
114
  return false
119
115
  end
120
116
 
@@ -144,7 +140,7 @@ module Sisimai
144
140
  value['by'] = cr[1]
145
141
  end
146
142
 
147
- if value['from'] =~ / /
143
+ if value['from'].include?(' ')
148
144
  # Received: from [10.22.22.222] (smtp-gateway.kyoto.ocn.ne.jp [192.0.2.222])
149
145
  # (authenticated bits=0)
150
146
  # by nijo.example.jp (V8/cf) with ESMTP id s1QB5ka0018055;
@@ -155,13 +151,12 @@ module Sisimai
155
151
  hostname = ''
156
152
  hostaddr = ''
157
153
 
158
- received.each do |e|
154
+ while e = received.shift do
159
155
  # Received: from [10.22.22.222] (smtp-gateway.kyoto.ocn.ne.jp [192.0.2.222])
160
156
  if e =~ /\A[\[(]\d+[.]\d+[.]\d+[.]\d+[)\]]\z/
161
157
  # [192.0.2.1] or (192.0.2.1)
162
158
  e = e.delete('[]()')
163
159
  addrlist << e
164
-
165
160
  else
166
161
  # hostname
167
162
  e = e.delete('()')
@@ -169,9 +164,9 @@ module Sisimai
169
164
  end
170
165
  end
171
166
 
172
- namelist.each do |e|
167
+ while e = namelist.shift do
173
168
  # 1. Hostname takes priority over all other IP addresses
174
- next unless e =~ /[.]/
169
+ next unless e.include?('.')
175
170
  hostname = e
176
171
  break
177
172
  end
@@ -180,9 +175,8 @@ module Sisimai
180
175
  # 2. Use IP address as a remote host name
181
176
  addrlist.each do |e|
182
177
  # Skip if the address is a private address
183
- next if e =~ /\A(?:10|127)[.]/
178
+ next if e.start_with?('10.', '127.', '192.168.')
184
179
  next if e =~ /\A172[.](?:1[6-9]|2[0-9]|3[0-1])[.]/
185
- next if e =~ /\A192[.]168[.]/
186
180
  hostaddr = e
187
181
  break
188
182
  end
@@ -210,7 +204,7 @@ module Sisimai
210
204
  rfc822part = '' # (String) message/rfc822-headers part
211
205
  previousfn = '' # (String) Previous field name
212
206
 
213
- argv1.each do |e|
207
+ while e = argv1.shift do
214
208
  # After "message/rfc822"
215
209
  if cv = e.match(/\A([-0-9A-Za-z]+?)[:][ ]*.+\z/)
216
210
  # Get required headers
@@ -219,13 +213,12 @@ module Sisimai
219
213
  next unless HeaderIndex.key?(lhs)
220
214
 
221
215
  previousfn = lhs
222
- rfc822part += e + "\n"
216
+ rfc822part << e + "\n"
223
217
 
224
- elsif e =~ /\A[ \t]+/
218
+ elsif e.start_with?(' ', "\t")
225
219
  # Continued line from the previous line
226
220
  next if rfc822next[previousfn]
227
- rfc822part += e + "\n" if LongHeaders.key?(previousfn)
228
-
221
+ rfc822part << e + "\n" if LongHeaders.key?(previousfn)
229
222
  else
230
223
  # Check the end of headers in rfc822 part
231
224
  next unless LongHeaders.key?(previousfn)
@@ -7,10 +7,13 @@ module Sisimai
7
7
  class << self
8
8
  # Imported from p5-Sisimail/lib/Sisimai/Rhost.pm
9
9
  RhostClass = {
10
- %r/\Aaspmx[.]l[.]google[.]com\z/ => 'GoogleApps',
11
- %r/[.](?:prod|protection)[.]outlook[.]com\z/ => 'ExchangeOnline',
12
- %r/\A(?:smtp|mailstore1)[.]secureserver[.]net\z/ => 'GoDaddy',
13
- %r/\b(?:laposte[.]net|orange[.]fr)\z/ => 'FrancePTT',
10
+ 'aspmx.l.google.com' => 'GoogleApps',
11
+ '.prod.outlook.com' => 'ExchangeOnline',
12
+ '.protection.outlook.com' => 'ExchangeOnline',
13
+ 'smtp.secureserver.net' => 'GoDaddy',
14
+ 'mailstore1.secureserver.net' => 'GoDaddy',
15
+ 'laposte.net' => 'FrancePTT',
16
+ 'orange.fr' => 'FrancePTT',
14
17
  }.freeze
15
18
 
16
19
  # Retrun the list of remote hosts Sisimai support
@@ -32,7 +35,7 @@ module Sisimai
32
35
 
33
36
  RhostClass.each_key do |e|
34
37
  # Try to match with each key of RhostClass
35
- next unless host0 =~ e
38
+ next unless host0.end_with?(e)
36
39
  match = true
37
40
  break
38
41
  end
@@ -53,8 +56,8 @@ module Sisimai
53
56
 
54
57
  RhostClass.each_key do |e|
55
58
  # Try to match with each key of RhostClass
56
- next unless remotehost =~ e
57
- modulename = 'Sisimai::Rhost::' + RhostClass[e]
59
+ next unless remotehost.end_with?(e)
60
+ modulename = 'Sisimai::Rhost::' << RhostClass[e]
58
61
  rhostclass = modulename.gsub('::', '/').downcase
59
62
  break
60
63
  end
@@ -8,254 +8,103 @@ module Sisimai
8
8
  # Imported from p5-Sisimail/lib/Sisimai/Rhost/ExchangeOnline.pm
9
9
 
10
10
  # https://technet.microsoft.com/en-us/library/bb232118
11
- CodeTable = {
12
- %r/\A4[.]3[.]1\z/ => [
13
- {
14
- :reason => 'systemfull',
15
- :regexp => %r/Insufficient system resources/,
16
- },
17
- ],
18
- %r/\A4[.]3[.]2\z/ => [
19
- {
20
- :reason => 'notaccept',
21
- :regexp => %r/System not accepting network messages/,
22
- },
23
- ],
11
+ StatusList = {
12
+ '4.3.1' => [{ reason: 'systemfull', string: 'Insufficient system resources' }],
13
+ '4.3.2' => [{ reason: 'notaccept', string: 'System not accepting network messages' }],
14
+ '4.4.2' => [{ reason: 'blocked', string: 'Connection dropped' }],
15
+ '4.7.26' => [{
16
+ reason: 'securityerror',
17
+ string: 'must pass either SPF or DKIM validation, this message is not signed'
18
+ }],
19
+ '5.0.0' => [{ reason: 'blocked', string: 'HELO / EHLO requires domain address' }],
20
+ '5.1.4' => [{ reason: 'systemerror', string: 'Destination mailbox address ambiguous' }],
21
+ '5.2.1' => [{ reason: 'suspend', string: 'Mailbox cannot be accessed' }],
22
+ '5.2.2' => [{ reason: 'mailboxfull', string: 'Mailbox full' }],
23
+ '5.2.3' => [{ reason: 'exceedlimit', string: 'Message too large' }],
24
+ '5.2.4' => [{ reason: 'systemerror', string: 'Mailing list expansion problem' }],
25
+ '5.3.3' => [{ reason: 'systemfull', string: 'Unrecognized command' }],
26
+ '5.3.4' => [{ reason: 'mesgtoobig', string: 'Message too big for system' }],
27
+ '5.3.5' => [{ reason: 'systemerror', string: 'System incorrectly configured' }],
28
+ '5.4.1' => [{ reason: 'userunknown', string: 'Recipient address rejected: Access denied' }],
29
+ '5.4.14' => [{ reason: 'networkerror',string: 'Hop count exceeded' }],
30
+ '5.5.2' => [{ reason: 'syntaxerror', string: 'Send hello first' }],
31
+ '5.5.3' => [{ reason: 'syntaxerror', string: 'Too many recipients' }],
32
+ '5.5.4' => [{ reason: 'filtered', string: 'Invalid domain name' }],
33
+ '5.5.6' => [{ reason: 'contenterror',string: 'Invalid message content' }],
34
+ '5.7.1' => [
35
+ { reason: 'securityerror', string: 'Delivery not authorized' },
36
+ { reason: 'securityerror', string: 'Client was not authenticated' },
37
+ { reason: 'norelaying', string: 'Unable to relay' },
38
+ ],
39
+ '5.7.25' => [{ reason: 'blocked', string: 'must have a reverse DNS record' }],
40
+ '5.7.506' => [{ reason: 'blocked', string: 'Bad HELO' }],
41
+ '5.7.508' => [{ reason: 'toomanyconn', string: 'has exceeded permitted limits within ' }],
42
+ '5.7.509' => [{ reason: 'rejected', string: 'does not pass DMARC verification' }],
43
+ '5.7.510' => [{ reason: 'notaccept', string: 'does not accept email over IPv6' }],
44
+ '5.7.511' => [{ reason: 'blocked', string: 'banned sender' }],
45
+ '5.7.512' => [{ reason: 'contenterror', string: 'message must be RFC 5322' }],
46
+ }.freeze
47
+ ReStatuses = {
24
48
  %r/\A4[.]4[.][17]\z/ => [
25
- {
26
- :reason => 'expired',
27
- :regexp => %r/(?:Connection[ ]timed[ ]out|Message[ ]expired)/x,
28
- },
29
- ],
30
- %r/\A4[.]4[.]2\z/ => [
31
- {
32
- :reason => 'blocked',
33
- :regexp => %r/Connection dropped/,
34
- },
35
- ],
36
- %r/\A4[.]7[.]26\z/ => [
37
- {
38
- :reason => 'securityerror',
39
- :regexp => %r/Access denied, a message sent over IPv6 .+ must pass either SPF or DKIM validation, this message is not signed/,
40
- },
49
+ { reason: 'expired', string: ['Connection timed out', 'Message expired'] }
41
50
  ],
42
- %r/\A4[.]7[.][568]\d{2}\z/ => [
43
- {
44
- :reason => 'securityerror',
45
- :regexp => %r/Access denied, please try again later/,
46
- },
47
- ],
48
- %r/\A5[.]0[.]0\z/ => [
49
- {
50
- :reason => 'blocked',
51
- :regexp => %r|HELO / EHLO requires domain address|,
52
- },
51
+ %r/\A4[.]7[.][568]\d\d\z/ => [
52
+ { reason: 'securityerror', string: ['Access denied, please try again later'] }
53
53
  ],
54
54
  %r/\A5[.]1[.][07]\z/ => [
55
- {
56
- :reason => 'rejected',
57
- :regexp => %r/(?:Sender[ ]denied|Invalid[ ]address)/x,
58
- },
59
- ],
60
- %r/\A5[.]1[.][123]\z/ => [
61
- {
62
- :reason => 'userunknown',
63
- :regexp => %r{(?:
64
- Bad[ ]destination[ ]mailbox[ ]address
65
- |Invalid[ ]X[.]400[ ]address
66
- |Invalid[ ]recipient[ ]address
67
- )
68
- }x,
69
- },
70
- ],
71
- %r/\A5[.]1[.]4\z/ => [
72
- {
73
- :reason => 'systemerror',
74
- :regexp => %r/Destination mailbox address ambiguous/,
75
- },
76
- ],
77
- %r/\A5[.]2[.]1\z/ => [
78
- {
79
- :reason => 'suspend',
80
- :regexp => %r/Mailbox cannot be accessed/,
81
- },
82
- ],
83
- %r/\A5[.]2[.]2\z/ => [
84
- {
85
- :reason => 'mailboxfull',
86
- :regexp => %r/Mailbox full/,
87
- },
88
- ],
89
- %r/\A5[.]2[.]3\z/ => [
90
- {
91
- :reason => 'exceedlimit',
92
- :regexp => %r/Message too large/,
93
- },
94
- ],
95
- %r/\A5[.]2[.]4\z/ => [
96
- {
97
- :reason => 'systemerror',
98
- :regexp => %r/Mailing list expansion problem/,
99
- },
100
- ],
101
- %r/\A5[.]3[.]3\z/ => [
102
- {
103
- :reason => 'systemfull',
104
- :regexp => %r/Unrecognized command/,
105
- },
106
- ],
107
- %r/\A5[.]3[.]4\z/ => [
108
- {
109
- :reason => 'mesgtoobig',
110
- :regexp => %r/Message too big for system/,
111
- },
112
- ],
113
- %r/\A5[.]3[.]5\z/ => [
114
- {
115
- :reason => 'systemerror',
116
- :regexp => %r/System incorrectly configured/,
117
- },
118
- ],
119
- %r/\A5[.]4[.]1\z/ => [
120
- {
121
- :reason => 'userunknown',
122
- :regexp => %r/Recipient address rejected: Access denied/,
123
- },
124
- ],
125
- %r/\A5[.]4[.][46]\z/ => [
126
- {
127
- :reason => 'networkerror',
128
- :regexp => %r/(?:Invalid[ ]arguments|Routing[ ]loop[ ]detected)/x,
129
- },
130
- ],
131
- %r/\A5[.]4[.]14\z/ => [
132
- {
133
- :reason => 'networkerror',
134
- :regexp => %r/Hop[ ]count[ ]exceeded/x
135
- },
136
- ],
137
- %r/\A5[.]5[.]2\z/ => [
138
- {
139
- :reason => 'syntaxerror',
140
- :regexp => %r/Send hello first/,
141
- },
142
- ],
143
- %r/\A5[.]5[.]3\z/ => [
144
- {
145
- :reason => 'syntaxerror',
146
- :regexp => %r/Too many recipients/,
147
- },
148
- ],
149
- %r/\A5[.]5[.]4\z/ => [
150
- {
151
- :reason => 'filtered',
152
- :regexp => %r/Invalid domain name/,
153
- },
154
- ],
155
- %r/\A5[.]5[.]6\z/ => [
156
- {
157
- :reason => 'contenterror',
158
- :regexp => %r/Invalid message content/,
159
- },
160
- ],
161
- %r/\A5[.]7[.][13]\z/ => [
162
- {
163
- :reason => 'securityerror',
164
- :regexp => %r/(?:Delivery[ ]not[ ]authorized|Not[ ]Authorized)/x,
165
- },
166
- ],
167
- %r/\A5[.]7[.]1\z/ => [
168
- {
169
- :reason => 'securityerror',
170
- :regexp => %r/(?:Delivery[ ]not[ ]authorized|Client[ ]was[ ]not[ ]authenticated)/x,
171
- },
172
- {
173
- :reason => 'norelaying',
174
- :regexp => %r/Unable to relay/,
175
- },
176
- ],
177
- %r/\A5[.]7[.]25\z/ => [
178
- {
179
- :reason => 'securityerror',
180
- :regexp => %r/Access denied, the sending IPv6 address .+ must have a reverse DNS record/,
181
- },
182
- ],
183
- %r/\A5[.]7[.]50[1-3]\z/ => [
184
- {
185
- :reason => 'spamdetected',
186
- :regexp => %r/Access[ ]denied,[ ](?:spam[ ]abuse[ ]detected|banned[ ]sender)/x,
187
- },
188
- ],
189
- %r/\A5[.]7[.]50[457]\z/ => [
190
- {
191
- :reason => 'filtered',
192
- :regexp => %r{(?>
193
- Recipient[ ]address[ ]rejected:[ ]Access[ ]denied
194
- |Access[ ]denied,[ ](?:banned[ ]recipient|rejected[ ]by[ ]recipient)
195
- )
196
- },
197
- },
198
- ],
199
- %r/\A5[.]7[.]506\z/ => [
200
- {
201
- :reason => 'blocked',
202
- :regexp => %r/Access Denied, Bad HELO/,
203
- },
204
- ],
205
- %r/\A5[.]7[.]508\z/ => [
206
- {
207
- :reason => 'toomanyconn',
208
- :regexp => %r/Access denied, .+ has exceeded permitted limits within .+ range/,
209
- },
210
- ],
211
- %r/\A5[.]7[.]509\z/ => [
212
- {
213
- :reason => 'securityerror',
214
- :regexp => %r/Access denied, sending domain .+ does not pass DMARC verification/,
215
- },
216
- ],
217
- %r/\A5[.]7[.]510\z/ => [
218
- {
219
- :reason => 'notaccept',
220
- :regexp => %r/Access denied, .+ does not accept email over IPv6/,
221
- },
222
- ],
223
- %r/\A5[.]7[.]511\z/ => [
224
- {
225
- :reason => 'blocked',
226
- :regexp => %r/Access denied, banned sender/,
227
- },
228
- ],
229
- %r/\A5[.]7[.]512\z/ => [
230
- {
231
- :reason => 'contenterror',
232
- :regexp => %r/Access denied, message must be RFC 5322 section 3[.]6[.]2 compliant/,
233
- },
234
- ],
235
- %r/\A5[.]7[.]6\d{2}\z/ => [
236
- {
237
- :reason => 'blocked',
238
- :regexp => %r/Access[ ]denied,[ ]banned[ ]sending[ ]IP[ ].+/,
239
- },
240
- ],
241
- %r/\A5[.]7[.]7\d{2}\z/ => [
242
- {
243
- :reason => 'toomanyconn',
244
- :regexp => %r/Access denied, tenant has exceeded threshold/,
245
- },
55
+ { reason: 'rejected', string: ['Sender denied', 'Invalid address'] }
56
+ ],
57
+ %r/\A5[.]1[.][123]\z/ => [{
58
+ reason: 'userunknown',
59
+ string: [
60
+ 'Bad destination mailbox address',
61
+ 'Invalid X.400 address',
62
+ 'Invalid recipient address',
63
+ ]
64
+ }],
65
+ %r/\A5[.]4[.][46]\z/ => [{
66
+ reason: 'networkerror',
67
+ string: ['Invalid arguments', 'Routing loop detected'],
68
+ }],
69
+ %r/\A5[.]7[.][13]\z/ => [{
70
+ reason: 'securityerror',
71
+ string: ['Delivery not authorized', 'Not Authorized'],
72
+ }],
73
+ %r/\A5[.]7[.]50[1-3]\z/ => [{
74
+ reason: 'spamdetected',
75
+ string: ['Access denied, spam abuse detected', 'Access denied, banned sender'],
76
+ }],
77
+ %r/\A5[.]7[.]50[457]\z/ => [{
78
+ reason: 'filtered',
79
+ string: [
80
+ 'Recipient address rejected: Access denied',
81
+ 'Access denied, banned recipient',
82
+ 'Access denied, rejected by recipient'
83
+ ]
84
+ }],
85
+ %r/\A5[.]7[.]6\d\d\z/ => [
86
+ { reason: 'blocked', string: ['Access denied, banned sending IP '] }
87
+ ],
88
+ %r/\A5[.]7[.]7\d\d\z/ => [
89
+ { reason: 'toomanyconn', string: ['Access denied, tenant has exceeded threshold'] }
246
90
  ],
247
91
  }.freeze
248
- MesgTable = {
92
+ MessagesOf = {
249
93
  # Copied and converted from Sisimai::Bite::Email::Exchange2007
250
- :expired => %r/QUEUE[.]Expired/,
251
- :hostunknown => %r/SMTPSEND[.]DNS[.]NonExistentDomain/,
252
- :mesgtoobig => %r/RESOLVER[.]RST[.]Recip(?:ient)?SizeLimit/,
253
- :networkerror => %r/SMTPSEND[.]DNS[.]MxLoopback/,
254
- :rejected => %r/RESOLVER[.]RST[.]NotAuthorized/,
255
- :securityerror => %r/RESOLVER[.]RST[.]AuthRequired/,
256
- :systemerror => %r/RESOLVER[.]ADR[.](?:Ambiguous|BadPrimary|InvalidInSmtp)/,
257
- :toomanyconn => %r/RESOLVER[.]ADR[.]Recip(?:ient)?Limit/,
258
- :userunknown => %r/RESOLVER[.]ADR[.](?:Ex)?Recip(?:ient)?NotFound/,
94
+ expired: ['QUEUE.Expired'],
95
+ hostunknown: ['SMTPSEND.DNS.NonExistentDomain'],
96
+ mesgtoobig: ['RESOLVER.RST.RecipSizeLimit', 'RESOLVER.RST.RecipientSizeLimit'],
97
+ networkerror: ['SMTPSEND.DNS.MxLoopback'],
98
+ rejected: ['RESOLVER.RST.NotAuthorized'],
99
+ securityerror: ['RESOLVER.RST.AuthRequired'],
100
+ systemerror: ['RESOLVER.ADR.Ambiguous', 'RESOLVER.ADR.BadPrimary', 'RESOLVER.ADR.InvalidInSmtp'],
101
+ toomanyconn: ['RESOLVER.ADR.RecipLimit', 'RESOLVER.ADR.RecipientLimit'],
102
+ userunknown: [
103
+ 'RESOLVER.ADR.RecipNotFound',
104
+ 'RESOLVER.ADR.RecipientNotFound',
105
+ 'RESOLVER.ADR.ExRecipNotFound',
106
+ 'RESOLVER.ADR.ExRecipientNotFound',
107
+ ],
259
108
  }.freeze
260
109
 
261
110
  # Detect bounce reason from Exchange Online
@@ -270,28 +119,48 @@ module Sisimai
270
119
  statusmesg = argvs.diagnosticcode
271
120
  reasontext = ''
272
121
 
273
- CodeTable.each_key do |e|
274
- # Try to match with each regular expression of delivery status codes
275
- next unless statuscode =~ e
276
- CodeTable[e].each do |f|
277
- # Try to match with each regular expression of error messages
278
- next unless statusmesg =~ f[:regexp]
122
+ StatusList.each_key do |e|
123
+ # Try to compare with each status code as a key
124
+ next unless statuscode == e
125
+ StatusList[e].each do |f|
126
+ # Try to compare with each string of error messages
127
+ next unless statusmesg.include?(f[:string])
279
128
  reasontext = f[:reason]
280
129
  break
281
130
  end
131
+ break if reasontext.size > 0
282
132
  end
283
133
 
284
134
  if reasontext.empty?
285
- # D.S.N. included in the error message did not matched with any key
286
- # in CodeTable
287
- MesgTable.each_key do |e|
288
- # Try to match with error messages defined in MesgTable
289
- next unless statusmesg =~ MesgTable[e]
290
- reasontext = e.to_s
291
- break
135
+ ReStatuses.each_key do |e|
136
+ # Try to compare with each string of delivery status codes
137
+ next unless statuscode =~ e
138
+ ReStatuses[e].each do |f|
139
+ # Try to compare with each string of error messages
140
+ f[:string].each do |g|
141
+ next unless statusmesg.include?(g)
142
+ reasontext = f[:reason]
143
+ break
144
+ end
145
+ break if reasontext.size > 0
146
+ end
147
+ break if reasontext.size > 0
292
148
  end
293
- end
294
149
 
150
+ if reasontext.empty?
151
+ # D.S.N. included in the error message did not matched with any
152
+ # key in ReStatuses
153
+ MessagesOf.each_key do |e|
154
+ # Try to compare with error messages defined in MessagesOf
155
+ MessagesOf[e].each do |f|
156
+ next unless statusmesg.include?(f)
157
+ reasontext = e.to_s
158
+ break
159
+ end
160
+ break if reasontext.size > 0
161
+ end
162
+ end
163
+ end
295
164
  return reasontext
296
165
  end
297
166