sisimai 5.0.0-java → 5.0.3-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codecovio.yml +33 -0
  3. data/.github/workflows/rake-test.yml +54 -0
  4. data/ChangeLog.md +68 -1
  5. data/Gemfile +2 -0
  6. data/README-JA.md +224 -112
  7. data/README.md +56 -33
  8. data/lib/sisimai/arf.rb +24 -5
  9. data/lib/sisimai/fact.rb +46 -8
  10. data/lib/sisimai/lhost/amazonses.rb +0 -1
  11. data/lib/sisimai/lhost/amazonworkmail.rb +0 -1
  12. data/lib/sisimai/lhost/aol.rb +0 -1
  13. data/lib/sisimai/lhost/bigfoot.rb +0 -1
  14. data/lib/sisimai/lhost/domino.rb +0 -1
  15. data/lib/sisimai/lhost/exchange2007.rb +1 -1
  16. data/lib/sisimai/lhost/exim.rb +7 -16
  17. data/lib/sisimai/lhost/facebook.rb +0 -1
  18. data/lib/sisimai/lhost/googlegroups.rb +2 -1
  19. data/lib/sisimai/lhost/gsuite.rb +0 -1
  20. data/lib/sisimai/lhost/mailmarshalsmtp.rb +1 -1
  21. data/lib/sisimai/lhost/mailru.rb +8 -17
  22. data/lib/sisimai/lhost/messagelabs.rb +0 -1
  23. data/lib/sisimai/lhost/mfilter.rb +1 -1
  24. data/lib/sisimai/lhost/mxlogic.rb +8 -18
  25. data/lib/sisimai/lhost/office365.rb +1 -1
  26. data/lib/sisimai/lhost/outlook.rb +0 -1
  27. data/lib/sisimai/lhost/postfix.rb +0 -1
  28. data/lib/sisimai/lhost/receivingses.rb +0 -1
  29. data/lib/sisimai/lhost/sendgrid.rb +1 -3
  30. data/lib/sisimai/lhost/sendmail.rb +0 -1
  31. data/lib/sisimai/lhost/yandex.rb +0 -1
  32. data/lib/sisimai/message.rb +14 -5
  33. data/lib/sisimai/reason/authfailure.rb +1 -0
  34. data/lib/sisimai/reason/blocked.rb +3 -0
  35. data/lib/sisimai/reason/expired.rb +8 -0
  36. data/lib/sisimai/reason/filtered.rb +1 -0
  37. data/lib/sisimai/reason/mailboxfull.rb +4 -0
  38. data/lib/sisimai/reason/norelaying.rb +1 -0
  39. data/lib/sisimai/reason/rejected.rb +1 -1
  40. data/lib/sisimai/reason/securityerror.rb +1 -0
  41. data/lib/sisimai/reason/spamdetected.rb +1 -0
  42. data/lib/sisimai/reason/suspend.rb +5 -0
  43. data/lib/sisimai/reason/userunknown.rb +4 -1
  44. data/lib/sisimai/rfc5322.rb +120 -64
  45. data/lib/sisimai/rhost/google.rb +347 -71
  46. data/lib/sisimai/rhost/microsoft.rb +8 -0
  47. data/lib/sisimai/rhost/mimecast.rb +9 -2
  48. data/lib/sisimai/rhost/nttdocomo.rb +3 -3
  49. data/lib/sisimai/smtp/status.rb +3 -0
  50. data/lib/sisimai/version.rb +1 -1
  51. data/lib/sisimai.rb +12 -11
  52. data/set-of-emails/maildir/bsd/arf-26.eml +27 -0
  53. data/set-of-emails/maildir/bsd/lhost-sendmail-60.eml +85 -0
  54. data/set-of-emails/maildir/bsd/rhost-microsoft-04.eml +86 -0
  55. data/set-of-emails/maildir/bsd/rhost-microsoft-05.eml +83 -0
  56. metadata +13 -8
  57. data/.travis.yml +0 -23
@@ -168,7 +168,6 @@ module Sisimai::Lhost
168
168
  dscontents.each do |e|
169
169
  # Set default values if each value is empty.
170
170
  e['diagnosis'] ||= ''
171
- e['lhost'] ||= permessage['rhost']
172
171
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
173
172
 
174
173
  if anotherset['diagnosis']
@@ -103,7 +103,6 @@ module Sisimai::Lhost
103
103
 
104
104
  dscontents.each do |e|
105
105
  # Set default values if each value is empty.
106
- e['lhost'] ||= permessage['rhost']
107
106
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
108
107
 
109
108
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].tr("\n", ' '))
@@ -198,17 +198,26 @@ module Sisimai
198
198
  # Select and convert all the headers in $argv0. The following regular expression is based on
199
199
  # https://gist.github.com/xtetsuji/b080e1f5551d17242f6415aba8a00239
200
200
  headermaps = { 'subject' => '' }
201
- recvheader = []
201
+ receivedby = []
202
202
  argv0.scan(/^([\w-]+):[ ]*(.*?)\n(?![\s\t])/m) { |e| headermaps[e[0].downcase] = e[1] }
203
203
  headermaps.delete('received')
204
204
  headermaps.each_key { |e| headermaps[e].gsub!(/\n[\s\t]+/, ' ') }
205
205
 
206
206
  if argv0.include?('Received:')
207
207
  # Capture values of each Received: header
208
- recvheader = argv0.scan(/^Received:[ ]*(.*?)\n(?![\s\t])/m).flatten
209
- recvheader.each { |e| e.gsub!(/\n[\s\t]+/, ' ') }
208
+ re = argv0.scan(/^Received:[ ]*(.*?)\n(?![\s\t])/m).flatten
209
+ re.each do |e|
210
+ # 1. Exclude the Received header including "(qmail ** invoked from network)".
211
+ # 2. Convert all consecutive spaces and line breaks into a single space character.
212
+ next if e.include?(' invoked by uid')
213
+ next if e.include?(' invoked from network')
214
+
215
+ e.gsub!(/\n[\s\t]+/, ' ')
216
+ e.squeeze!("\n\t ")
217
+ receivedby << e
218
+ end
210
219
  end
211
- headermaps['received'] = recvheader
220
+ headermaps['received'] = receivedby
212
221
 
213
222
  return headermaps unless argv1
214
223
  return headermaps if headermaps['subject'].empty?
@@ -443,7 +452,7 @@ module Sisimai
443
452
  unless haveloaded['Sisimai::ARF']
444
453
  # Feedback Loop message
445
454
  require 'sisimai/arf'
446
- havesifted = Sisimai::ARF.inquire(mailheader, bodystring) if Sisimai::ARF.is_arf(mailheader)
455
+ havesifted = Sisimai::ARF.inquire(mailheader, bodystring)
447
456
  throw :PARSER if havesifted
448
457
  end
449
458
 
@@ -20,6 +20,7 @@ module Sisimai
20
20
  'dmarc policy',
21
21
  'please inspect your spf settings',
22
22
  'sender policy framework (spf) fail',
23
+ 'sender policy framework violation',
23
24
  'spf (sender policy framework) domain authentication fail',
24
25
  'spf check: fail',
25
26
  ].freeze
@@ -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',
@@ -61,6 +62,7 @@ module Sisimai
61
62
  'we do not accept mail from dynamic ips', # @mail.ru
62
63
  'you are not allowed to connect',
63
64
  'you are sending spam',
65
+ 'your ip address is listed in the rbl',
64
66
  'your network is temporary blacklisted',
65
67
  'your server requires confirmation',
66
68
  ].freeze
@@ -78,6 +80,7 @@ module Sisimai
78
80
  ['message from ', ' rejected based on blacklist'],
79
81
  ['messages from ', ' temporarily deferred due to user complaints'], # Yahoo!
80
82
  ['server ip ', ' listed as abusive'],
83
+ ['sorry! your ip address', ' is blocked by rbl'], # junkemailfilter.com
81
84
  ['the domain ', ' is blacklisted'],
82
85
  ['the email ', ' is blacklisted'],
83
86
  ['the ip', ' is blacklisted'],
@@ -7,10 +7,13 @@ module Sisimai
7
7
  # the message you sent has been in the queue for long time.
8
8
  module Expired
9
9
  class << self
10
+ require 'sisimai/string'
11
+
10
12
  Index = [
11
13
  'connection timed out',
12
14
  'could not find a gateway for',
13
15
  'delivery attempts will continue to be',
16
+ 'delivery expired',
14
17
  'delivery time expired',
15
18
  'failed to deliver to domain ',
16
19
  'giving up on',
@@ -18,6 +21,7 @@ module Sisimai
18
21
  'has been delayed',
19
22
  'it has not been collected after',
20
23
  'message expired after sitting in queue for',
24
+ 'message expired, cannot connect to remote server',
21
25
  'message expired, connection refulsed',
22
26
  'message timed out',
23
27
  'retry time not reached for any host after a long failure period',
@@ -27,6 +31,9 @@ module Sisimai
27
31
  'was not reachable within the allowed queue period',
28
32
  'your message could not be delivered for more than',
29
33
  ].freeze
34
+ Pairs = [
35
+ ['could not be delivered for', ' days'],
36
+ ].freeze
30
37
 
31
38
  def text; return 'expired'; end
32
39
  def description; return 'Delivery time has expired due to a connection failure'; end
@@ -38,6 +45,7 @@ module Sisimai
38
45
  def match(argv1)
39
46
  return nil unless argv1
40
47
  return true if Index.any? { |a| argv1.include?(a) }
48
+ return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) }
41
49
  return false
42
50
  end
43
51
 
@@ -21,6 +21,7 @@ module Sisimai
21
21
  'resolver.rst.notauthorized', # Microsoft Exchange
22
22
  'this account is protected by',
23
23
  'user not found', # Filter on MAIL.RU
24
+ 'user refuses to receive this mail',
24
25
  'user reject',
25
26
  'we failed to deliver mail because the following address recipient id refuse to receive mail', # Willcom
26
27
  'you have been blocked by the recipient',
@@ -8,6 +8,7 @@ module Sisimai
8
8
  module MailboxFull
9
9
  class << self
10
10
  Index = [
11
+ '452 insufficient disk space',
11
12
  'account disabled temporarly for exceeding receiving limits',
12
13
  'account is exceeding their quota',
13
14
  'account is over quota',
@@ -15,6 +16,7 @@ module Sisimai
15
16
  'boite du destinataire pleine',
16
17
  'delivery failed: over quota',
17
18
  'disc quota exceeded',
19
+ 'diskspace quota',
18
20
  'does not have enough space',
19
21
  'exceeded storage allocation',
20
22
  'exceeding its mailbox quota',
@@ -34,7 +36,9 @@ module Sisimai
34
36
  'maildir delivery failed: userdisk quota ',
35
37
  'maildir delivery failed: domaindisk quota ',
36
38
  'mailfolder is full',
39
+ 'no space left on device',
37
40
  'not enough storage space in',
41
+ 'not sufficient disk space',
38
42
  'over the allowed quota',
39
43
  'quota exceeded',
40
44
  'quota violation for',
@@ -24,6 +24,7 @@ module Sisimai
24
24
  'relay not permitted',
25
25
  'relaying denied', # Sendmail
26
26
  'relaying mail to ',
27
+ 'specified domain is not allowed',
27
28
  "that domain isn't in my list of allowed rcpthost",
28
29
  'this system is not configured to relay mail',
29
30
  'unable to relay for',
@@ -38,7 +38,7 @@ module Sisimai
38
38
  'email address is on senderfilterconfig list',
39
39
  'emetteur invalide',
40
40
  'empty envelope senders not allowed',
41
- 'envelope blocked ',
41
+ 'envelope blocked - ',
42
42
  'error: no third-party dsns', # SpamWall - block empty sender
43
43
  'from: domain is invalid. please provide a valid from:',
44
44
  'fully qualified email address required', # McAfee
@@ -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',
@@ -12,16 +12,21 @@ module Sisimai
12
12
  'boite du destinataire archivee',
13
13
  'email account that you tried to reach is disabled',
14
14
  'has been suspended',
15
+ 'inactive account',
15
16
  'invalid/inactive user',
16
17
  'is a deactivated mailbox', # http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000742
17
18
  'is unavailable: user is terminated',
18
19
  'mailbox currently suspended',
20
+ 'mailbox disabled',
19
21
  'mailbox is frozen',
20
22
  'mailbox unavailable or access denied',
21
23
  'recipient rejected: temporarily inactive',
22
24
  'recipient suspend the service',
23
25
  'this account has been disabled or discontinued',
26
+ 'this account has been temporarily suspended',
27
+ 'this address no longer accepts mail',
24
28
  'this mailbox is disabled',
29
+ 'user or domain is disabled',
25
30
  'user suspended', # http://mail.163.com/help/help_spam_16.htm
26
31
  'vdelivermail: account is locked email bounced',
27
32
  ].freeze
@@ -63,7 +63,6 @@ module Sisimai
63
63
  'no such mailbox',
64
64
  'no such person at this address',
65
65
  'no such recipient',
66
-
67
66
  'no such user',
68
67
  'no thank you rejected: account unavailable',
69
68
  'no valid recipients, bye',
@@ -76,7 +75,9 @@ module Sisimai
76
75
  'recipient address rejected: invalid user',
77
76
  'recipient address rejected: invalid-recipient',
78
77
  'recipient address rejected: unknown user',
78
+ 'recipient address rejected: userunknown',
79
79
  'recipient does not exist',
80
+ 'recipient is not accepted',
80
81
  'recipient is not local',
81
82
  'recipient not exist',
82
83
  'recipient not found',
@@ -125,6 +126,7 @@ module Sisimai
125
126
  ['no ', ' in name directory'],
126
127
  ['non', 'existent user'],
127
128
  ['rcpt <', ' does not exist'],
129
+ ['rcpt (', 't exist '],
128
130
  ['recipient ', ' was not found in'],
129
131
  ['recipient address rejected: user ', ' does not exist'],
130
132
  ['recipient address rejected: user unknown in ', ' table'],
@@ -135,6 +137,7 @@ module Sisimai
135
137
  ['unknown local', 'part'],
136
138
  ['user ', ' was not found'],
137
139
  ['user ', ' does not exist'],
140
+ ['user (', ') unknown'],
138
141
  ].freeze
139
142
 
140
143
  def text; return 'userunknown'; end
@@ -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