sisimai 5.0.0 → 5.0.2

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake-test.yml +55 -0
  3. data/ChangeLog.md +40 -1
  4. data/README-JA.md +223 -111
  5. data/README.md +54 -31
  6. data/lib/sisimai/fact.rb +46 -8
  7. data/lib/sisimai/lhost/amazonses.rb +0 -1
  8. data/lib/sisimai/lhost/amazonworkmail.rb +0 -1
  9. data/lib/sisimai/lhost/aol.rb +0 -1
  10. data/lib/sisimai/lhost/bigfoot.rb +0 -1
  11. data/lib/sisimai/lhost/domino.rb +0 -1
  12. data/lib/sisimai/lhost/exchange2007.rb +1 -1
  13. data/lib/sisimai/lhost/exim.rb +7 -16
  14. data/lib/sisimai/lhost/facebook.rb +0 -1
  15. data/lib/sisimai/lhost/googlegroups.rb +2 -1
  16. data/lib/sisimai/lhost/gsuite.rb +0 -1
  17. data/lib/sisimai/lhost/mailru.rb +8 -17
  18. data/lib/sisimai/lhost/messagelabs.rb +0 -1
  19. data/lib/sisimai/lhost/mfilter.rb +1 -1
  20. data/lib/sisimai/lhost/mxlogic.rb +8 -18
  21. data/lib/sisimai/lhost/office365.rb +1 -1
  22. data/lib/sisimai/lhost/outlook.rb +0 -1
  23. data/lib/sisimai/lhost/postfix.rb +0 -1
  24. data/lib/sisimai/lhost/receivingses.rb +0 -1
  25. data/lib/sisimai/lhost/sendgrid.rb +1 -3
  26. data/lib/sisimai/lhost/sendmail.rb +0 -1
  27. data/lib/sisimai/lhost/yandex.rb +0 -1
  28. data/lib/sisimai/message.rb +13 -4
  29. data/lib/sisimai/reason/authfailure.rb +1 -0
  30. data/lib/sisimai/reason/blocked.rb +2 -0
  31. data/lib/sisimai/reason/expired.rb +1 -0
  32. data/lib/sisimai/reason/mailboxfull.rb +1 -0
  33. data/lib/sisimai/reason/securityerror.rb +1 -0
  34. data/lib/sisimai/reason/spamdetected.rb +1 -0
  35. data/lib/sisimai/reason/suspend.rb +1 -0
  36. data/lib/sisimai/rfc5322.rb +120 -64
  37. data/lib/sisimai/rhost/google.rb +320 -59
  38. data/lib/sisimai/rhost/mimecast.rb +9 -2
  39. data/lib/sisimai/smtp/status.rb +3 -0
  40. data/lib/sisimai/version.rb +1 -1
  41. data/lib/sisimai.rb +12 -11
  42. data/set-of-emails/maildir/bsd/lhost-sendmail-60.eml +85 -0
  43. metadata +4 -2
@@ -46,6 +46,7 @@ module Sisimai
46
46
  'part of their network is on our block list',
47
47
  'please use the smtp server of your isp',
48
48
  'refused - see http',
49
+ 'rejected - multi-blacklist', # junkemailfilter.com
49
50
  'rejected because the sending mta or the sender has not passed validation',
50
51
  'rejecting open proxy', # Sendmail(srvrsmtp.c)
51
52
  'sender ip address rejected',
@@ -78,6 +79,7 @@ module Sisimai
78
79
  ['message from ', ' rejected based on blacklist'],
79
80
  ['messages from ', ' temporarily deferred due to user complaints'], # Yahoo!
80
81
  ['server ip ', ' listed as abusive'],
82
+ ['sorry! your ip address', ' is blocked by rbl'], # junkemailfilter.com
81
83
  ['the domain ', ' is blacklisted'],
82
84
  ['the email ', ' is blacklisted'],
83
85
  ['the ip', ' is blacklisted'],
@@ -18,6 +18,7 @@ module Sisimai
18
18
  'has been delayed',
19
19
  'it has not been collected after',
20
20
  'message expired after sitting in queue for',
21
+ 'message expired, cannot connect to remote server',
21
22
  'message expired, connection refulsed',
22
23
  'message timed out',
23
24
  'retry time not reached for any host after a long failure period',
@@ -15,6 +15,7 @@ module Sisimai
15
15
  'boite du destinataire pleine',
16
16
  'delivery failed: over quota',
17
17
  'disc quota exceeded',
18
+ 'diskspace quota',
18
19
  'does not have enough space',
19
20
  'exceeded storage allocation',
20
21
  'exceeding its mailbox quota',
@@ -29,6 +29,7 @@ module Sisimai
29
29
  'insecure mail relay',
30
30
  'recipient address rejected: access denied',
31
31
  "sorry, you don't authenticate or the domain isn't in my list of allowed rcpthosts",
32
+ 'starttls is required to send mail',
32
33
  'tls required but not supported', # SendGrid:the recipient mailserver does not support TLS or have a valid certificate
33
34
  'unauthenticated senders not allowed',
34
35
  'verification failure',
@@ -76,6 +76,7 @@ module Sisimai
76
76
  'spam score ',
77
77
  'spambouncer identified spam', # SpamBouncer identified SPAM
78
78
  'spamming not allowed',
79
+ 'too many spam complaints',
79
80
  'too much spam.', # Earthlink
80
81
  'the email message was detected as spam',
81
82
  'the message has been rejected by spam filtering engine',
@@ -21,6 +21,7 @@ module Sisimai
21
21
  'recipient rejected: temporarily inactive',
22
22
  'recipient suspend the service',
23
23
  'this account has been disabled or discontinued',
24
+ 'this address no longer accepts mail',
24
25
  'this mailbox is disabled',
25
26
  'user suspended', # http://mail.163.com/help/help_spam_16.htm
26
27
  'vdelivermail: account is locked email bounced',
@@ -3,6 +3,7 @@ module Sisimai
3
3
  module RFC5322
4
4
  class << self
5
5
  require 'sisimai/string'
6
+ require 'sisimai/address'
6
7
  HeaderTable = {
7
8
  :messageid => %w[message-id],
8
9
  :subject => %w[subject],
@@ -52,79 +53,134 @@ module Sisimai
52
53
  # @return [Array] Received header as a structured data
53
54
  def received(argv1)
54
55
  return [] unless argv1.is_a?(::String)
56
+ return [] if argv1.include?(' invoked by uid')
57
+ return [] if argv1.include?(' invoked from network')
58
+
59
+ # - https://datatracker.ietf.org/doc/html/rfc5322
60
+ # received = "Received:" *received-token ";" date-time CRLF
61
+ # received-token = word / angle-addr / addr-spec / domain
62
+ #
63
+ # - Appendix A.4. Message with Trace Fields
64
+ # Received:
65
+ # from x.y.test
66
+ # by example.net
67
+ # via TCP
68
+ # with ESMTP
69
+ # id ABC12345
70
+ # for <mary@example.net>; 21 Nov 1997 10:05:43 -0600
71
+ recvd = argv1.split(' ')
72
+ label = %w[from by via with id for]
73
+ token = {}
74
+ other = []
75
+ alter = []
76
+ right = false
77
+ range = recvd.size
78
+ index = -1
79
+
80
+ recvd.each do |e|
81
+ # Look up each label defined in "label" from Received header
82
+ index += 1
83
+ break unless index < range; f = e.downcase
84
+ next unless label.any? { |a| f == a }
85
+ token[f] = recvd[index + 1] || next
86
+ token[f] = token[f].downcase.delete('();')
87
+
88
+ next unless f == 'from'
89
+ break unless index + 2 < range
90
+ next unless recvd[index + 2].start_with?('(')
91
+
92
+ # Get and keep a hostname in the comment as follows:
93
+ # from mx1.example.com (c213502.kyoto.example.ne.jp [192.0.2.135]) by mx.example.jp (V8/cf)
94
+ # [
95
+ # "from", # index + 0
96
+ # "mx1.example.com", # index + 1
97
+ # "(c213502.kyoto.example.ne.jp", # index + 2
98
+ # "[192.0.2.135])", # index + 3
99
+ # "by",
100
+ # "mx.example.jp",
101
+ # "(V8/cf)",
102
+ # ...
103
+ # ]
104
+ # The 2nd element after the current element is NOT a continuation of the current element
105
+ # such as "(c213502.kyoto.example.ne.jp)"
106
+ other << recvd[index + 2].delete('();')
107
+
108
+ # The 2nd element after the current element is a continuation of the current element.
109
+ # such as "(c213502.kyoto.example.ne.jp", "[192.0.2.135])"
110
+ break unless index + 3 < range
111
+ other << recvd[index + 3].delete('();')
112
+ end
55
113
 
56
- hosts = []
57
- value = { 'from' => '', 'by' => '' }
58
-
59
- # Received: (qmail 10000 invoked by uid 999); 24 Apr 2013 00:00:00 +0900
60
- return [] if argv1.include?('(qmail ') && argv1.include?(' invoked ')
61
-
62
- p1 = argv1.index('from ') || -1
63
- p2 = argv1.index('by ') || -1
64
- p3 = argv1.index(' ', p2 + 3) || -1
65
-
66
- if p1 == 0 && p2 > 1 && p2 < p3
67
- # Received: from localhost (localhost) by nijo.example.jp (V8/cf) id s1QB5ma0018057;
68
- # Wed, 26 Feb 2014 06:05:48 -0500
69
- value['from'] = Sisimai::String.sweep(argv1[p1 + 5, p2 - p1 - 5])
70
- value['by'] = Sisimai::String.sweep(argv1[p2 + 3, p3 - p2 - 3])
114
+ other.each do |e|
115
+ # Check alternatives in "other", and then delete uninformative values.
116
+ next if e.nil?
117
+ next if e.size < 4
118
+ next if e == 'unknown'
119
+ next if e == 'localhost'
120
+ next if e == '[127.0.0.1]'
121
+ next if e == '[IPv6:::1]'
122
+ next unless e.include?('.')
123
+ next if e.include?('=')
124
+ alter << e
125
+ end
71
126
 
72
- elsif p1 != 0 && p2 > -1
73
- # Received: by 10.70.22.98 with SMTP id c2mr1838265pdf.3; Fri, 18 Jul 2014 00:31:02 -0700 (PDT)
74
- value['from'] = Sisimai::String.sweep(argv1[p2 + 3, argv1.size])
75
- value['by'] = Sisimai::String.sweep(argv1[p2 + 3, p3 - p2 - 3])
127
+ %w[from by].each do |e|
128
+ # Remove square brackets from the IP address such as "[192.0.2.25]"
129
+ next if token[e].nil?
130
+ next if token[e].empty?
131
+ next unless token[e].start_with?('[')
132
+ token[e] = Sisimai::String.ipv4(token[e]).shift || ''
76
133
  end
134
+ token['from'] ||= ''
77
135
 
78
- if value['from'].include?(' ')
79
- # Received: from [10.22.22.222] (smtp.kyoto.ocn.ne.jp [192.0.2.222]) (authenticated bits=0)
80
- # by nijo.example.jp (V8/cf) with ESMTP id s1QB5ka0018055; Wed, 26 Feb 2014 06:05:47 -0500
81
- received = value['from'].split(' ')
82
- namelist = []
83
- addrlist = []
84
- hostname = ''
85
- hostaddr = ''
86
-
87
- while e = received.shift do
88
- # Received: from [10.22.22.222] (smtp-gateway.kyoto.ocn.ne.jp [192.0.2.222])
89
- cv = Sisimai::String.ipv4(e) || []
90
- if cv.size > 0
91
- # [192.0.2.1] or (192.0.2.1)
92
- addrlist.append(*cv)
93
- else
94
- # hostname
95
- e = e.delete('()').strip
96
- namelist << e
97
- end
98
- end
136
+ while true do
137
+ # Prefer hostnames over IP addresses, except for localhost.localdomain and similar.
138
+ break if token['from'] == 'localhost'
139
+ break if token['from'] == 'localhost.localdomain'
140
+ break unless token['from'].include?('.') # A hostname without a domain name
141
+ break unless Sisimai::String.ipv4(token['from']).empty?
99
142
 
100
- while e = namelist.shift do
101
- # 1. Hostname takes priority over all other IP addresses
102
- next unless e.include?('.')
103
- hostname = e
104
- break
105
- end
143
+ # No need to rewrite token['from']
144
+ right = true
145
+ break
146
+ end
106
147
 
107
- if hostname.empty?
108
- # 2. Use IP address as a remote host name
109
- addrlist.each do |e|
110
- # Skip if the address is a private address
111
- next if e.start_with?('10.', '127.', '192.168.')
112
- next if e =~ /\A172[.](?:1[6-9]|2[0-9]|3[0-1])[.]/
113
- hostaddr = e
114
- break
115
- end
148
+ while true do
149
+ # Try to rewrite uninformative hostnames and IP addresses in token['from']
150
+ break if right # There is no need to rewrite
151
+ break if alter.empty? # There is no alternative to rewriting
152
+ break if alter[0].include?(token['from'])
153
+
154
+ if token['from'].start_with?('localhost')
155
+ # localhost or localhost.localdomain
156
+ token['from'] = alter[0]
157
+ elsif token['from'].index('.')
158
+ # A hostname without a domain name such as "mail", "mx", or "mbox"
159
+ token['from'] = alter[0] if alter[0].include?('.')
160
+ else
161
+ # An IPv4 address
162
+ token['from'] = alter[0]
116
163
  end
117
-
118
- value['from'] = hostname || hostaddr || addrlist[-1]
164
+ break
119
165
  end
120
-
121
- %w[from by].each do |e|
122
- # Copy entries into hosts
123
- next if value[e].empty?
124
- value[e] = value[e].delete('[]();?')
125
- hosts << value[e]
166
+ token.delete('from') if token['from'].nil?
167
+ token.delete('by') if token['by'].nil?
168
+ token['for'] = Sisimai::Address.s3s4(token['for']) if token.has_key?('for')
169
+
170
+ token.keys.each do |e|
171
+ # Delete an invalid value
172
+ token[e] = '' if token[e].include?(' ')
173
+ token[e].delete!('[]') # Remove "[]" from the IP address
126
174
  end
127
- return hosts
175
+
176
+ return [
177
+ token['from'] || '',
178
+ token['by'] || '',
179
+ token['via'] || '',
180
+ token['with'] || '',
181
+ token['id'] || '',
182
+ token['for'] || '',
183
+ ]
128
184
  end
129
185
 
130
186
  # Split given entire message body into error message lines and the original message part only