sisimai 5.0.0 → 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
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