sisimai 5.0.1 → 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake-test.yml +55 -0
  3. data/ChangeLog.md +27 -0
  4. data/README-JA.md +13 -13
  5. data/README.md +13 -14
  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/mimecast.rb +9 -2
  38. data/lib/sisimai/version.rb +1 -1
  39. data/set-of-emails/maildir/bsd/lhost-sendmail-60.eml +85 -0
  40. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f3ea96ca0a6350d1b5b085191d287c061459de6be048ecc6cc1a115509adcef
4
- data.tar.gz: 2305366582fd23ba18f9db19d21b33c6d714f871435aa6b2601b3c421a749fe9
3
+ metadata.gz: 7090710f97dbdd17e7bf971fdad787f7eeab1656de2e93012e32430b9a7d64fb
4
+ data.tar.gz: 4dd096852de6cb0c9a44a78fc11058a86494fb4e032f8aa6c8a31bf65149cec8
5
5
  SHA512:
6
- metadata.gz: 795bf696fc7accdcb2835f9cdd719e3c61dc3b84a8dec864e65fc325a1499dde22593edba19584ba47bb6b485c90ce9fbfb49a962b3cd920695c6564eef28a9c
7
- data.tar.gz: 4b28f1877c47f788ba5ad72bce4a22931b651534c5c7096c3dd30c5c75f11fcc4401c605915e2becdf168015a471a04a73cf7b3fca0f85e1cfeb11073057a4b4
6
+ metadata.gz: d4f9752e00950e8965c6328ddea6f82f80e436e9e7788d41ab9e5484fce8a64be99c23d193d902f118d158eed5246b4601f4c9ce618cd219e04a943db79df860
7
+ data.tar.gz: ee66c1d20a46c49a11fc07c111039a3322fc89cd2e682d9b09a59040f35fa3d2a93348c975f7d6cb10884d29c80962df4e6c65e51e7a90dfe03b2fd0c36cad4e
@@ -0,0 +1,55 @@
1
+ name: rake test
2
+ on:
3
+ push:
4
+ branches: ["5-stable"]
5
+ pull_request:
6
+ branches: ["5-stable"]
7
+ jobs:
8
+ test-cruby:
9
+ name: rake test with CRuby ${{ matrix.cruby }}
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ cruby: ["2.4", "3.3"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - name: Setup CRuby
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: ${{ matrix.cruby }}
21
+ - run: gem install minitest -v 5.15.0
22
+ - run: gem install oj -v 3.10.0
23
+ - run: gem install bundle rake
24
+ - name: Cache the dependent gems
25
+ uses: actions/cache@v2
26
+ with:
27
+ path: ./cache
28
+ key: ${{ runner.os }}-ruby-${{ matrix.cruby }}-${{ hashFiles('./gem.snapshot') }}
29
+ - run: ruby -v
30
+ - run: rake publictest
31
+ - run: rake privatetest
32
+ test-jruby:
33
+ name: rake test with JRuby ${{ matrix.jruby }}
34
+ runs-on: ubuntu-latest
35
+ strategy:
36
+ fail-fast: false
37
+ matrix:
38
+ jruby: ["jruby-9.2", "jruby-9.4"]
39
+ steps:
40
+ - uses: actions/checkout@v2
41
+ - name: Setup JRuby
42
+ uses: ruby/setup-ruby@v1
43
+ with:
44
+ ruby-version: ${{ matrix.jruby }}
45
+ - run: gem install minitest -v 5.15.0
46
+ - run: gem install bundle rake jrjackson
47
+ - name: Cache the dependent gems
48
+ uses: actions/cache@v2
49
+ with:
50
+ path: ./cache
51
+ key: ${{ runner.os }}-ruby-${{ matrix.jruby }}-${{ hashFiles('./gem.snapshot') }}
52
+ - run: ruby -v
53
+ - run: rake publictest
54
+ - run: rake privatetest
55
+
data/ChangeLog.md CHANGED
@@ -3,6 +3,33 @@ RELEASE NOTES for Ruby version of Sisimai
3
3
  - releases: "https://github.com/sisimai/rb-sisimai/releases"
4
4
  - download: "https://rubygems.org/gems/sisimai"
5
5
 
6
+ v5.0.2
7
+ ---------------------------------------------------------------------------------------------------
8
+ - release: "Wed, 13 Mar 2024 13:00:00 +0900 (JST)"
9
+ - version: "5.0.2"
10
+ - changes:
11
+ - #271 #267 Sisimai 5 works on JRuby again. Thanks to @hiroyuki-sato
12
+ - #159 Implement workarounds at `Sisimai::Fact` and some public tests with `DateTime` class to
13
+ run Sisimai on JRuby, to avoid failing `strptime()`
14
+ - #269 Replace `Array#append` with `Array#push` at `Sisimai::RFC5322` because `Array#append` is
15
+ only available in Ruby 2.5 and above
16
+ - We have started testing JRuby 9.2 and 9.4 on GitHub Actions
17
+ - Disable 2 tests in `test/public/mail-test.rb` that fails on GitHub Actions only
18
+ - `5.7.23` returned from Office365 is an error related to SPF vilation (authfailure)
19
+ - #272 Fixed an issue that Sisimai could not get the value of `alias` address correctly when an
20
+ email forwarded and bounced
21
+ - `Sisimai::RFC5322.received` now returns a list including all the elements except date time and
22
+ (comments) found in the `Received` header
23
+ - Update the error message patterns in `Sisimai::Rhost::Mimecast`
24
+ - Update the error message patterns in the followings:
25
+ - `AuthFailure`
26
+ - `Blocked`
27
+ - `Expired`
28
+ - `MailboxFull`
29
+ - `SecurityError`
30
+ - `SpamDetected`
31
+ - `Suspend`
32
+
6
33
  v5.0.1
7
34
  ---------------------------------------------------------------------------------------------------
8
35
  - release: "Sun, 3 Mar 2024 17:17:17 +0900 (JST)"
data/README-JA.md CHANGED
@@ -102,7 +102,7 @@ Sisimaiの動作環境についての詳細は[Sisimai | シシマイを使っ
102
102
 
103
103
  * [Ruby 2.4.0 or later](http://www.ruby-lang.org/)
104
104
  * [__oj | The fastest JSON parser and object serializer__](https://rubygems.org/gems/oj)
105
- * Also works on [JRuby 9.0.4.0 - 9.1.17.0](http://jruby.org)
105
+ * Also works on [JRuby 9.2 or later](http://jruby.org)
106
106
  * [__jrjackson | A mostly native JRuby wrapper for the java jackson json processor jar__](https://rubygems.org/gems/jrjackson)
107
107
 
108
108
  Install
@@ -110,10 +110,10 @@ Install
110
110
  ### From RubyGems.org
111
111
  ```shell
112
112
  $ sudo gem install sisimai
113
- Fetching: sisimai-5.0.1.gem (100%)
114
- Successfully installed sisimai-5.0.1
115
- Parsing documentation for sisimai-5.0.1
116
- Installing ri documentation for sisimai-5.0.1
113
+ Fetching: sisimai-5.0.2.gem (100%)
114
+ Successfully installed sisimai-5.0.2
115
+ Parsing documentation for sisimai-5.0.2
116
+ Installing ri documentation for sisimai-5.0.2
117
117
  Done installing documentation for sisimai after 6 seconds
118
118
  1 gem installed
119
119
  ```
@@ -141,13 +141,13 @@ if [ -d "/usr/local/jr" ]; then \
141
141
  ...
142
142
  3 gems installed
143
143
  /opt/local/bin/rake install
144
- sisimai 5.0.0 built to pkg/sisimai-5.0.0.gem.
145
- sisimai (5.0.0) installed.
144
+ sisimai 5.0.2 built to pkg/sisimai-5.0.2.gem.
145
+ sisimai (5.0.2) installed.
146
146
  if [ -d "/usr/local/jr" ]; then \
147
147
  PATH="/usr/local/jr/bin:$PATH" /usr/local/jr/bin/rake install; \
148
148
  fi
149
- sisimai 5.0.0 built to pkg/sisimai-5.0.0-java.gem.
150
- sisimai (5.0.0) installed.
149
+ sisimai 5.0.2 built to pkg/sisimai-5.0.2-java.gem.
150
+ sisimai (5.0.2) installed.
151
151
  ```
152
152
 
153
153
  Usage
@@ -348,15 +348,15 @@ Sisimai 5.0.0から**Ruby 2.4以上**が必要になります。
348
348
 
349
349
  | 機能 | Sisimai 4 | Sisimai 5 |
350
350
  |------------------------------------------------------|--------------------|---------------------|
351
- | 動作環境(CRuby) | 2.1 - | **2.4** - 3.3.0 |
352
- | 動作環境(JRuby) | 9.0.4.0 - 9.1.17.0 | 9.0.4.0 - 9.1.17.0 |
351
+ | 動作環境(CRuby) | 2.1 - | **2.4** or later |
352
+ | 動作環境(JRuby) | 9.0.4.0 - 9.1.17.0 | **9.2** or later |
353
353
  | 元メールファイルを操作可能なコールバック機能 | なし | あり[^3] |
354
354
  | 解析エンジン(MTA/ESPモジュール)の数 | 68 | 70 |
355
355
  | 検出可能なバウンス理由の数 | 29 | 34 |
356
356
  | 依存Gem数(Ruby Standard Gemsを除く) | 1 Gem | 1 Gem |
357
- | ソースコードの行数 | 10,800 行 | 11,400 行 |
357
+ | ソースコードの行数 | 10,800 行 | 11,370 行 |
358
358
  | テストフレームワーク | rspec | minitest |
359
- | テスト件数(spec/またはtest/ディレクトリ) | 311,000 件 | 336,000 件 |
359
+ | テスト件数(spec/またはtest/ディレクトリ) | 311,000 件 | 338,000 件 |
360
360
  | 1秒間に解析できるバウンスメール数[^4] | 231 通 | 305 通 |
361
361
  | ライセンス | 2条項BSD | 2条項BSD |
362
362
  | 開発会社による商用サポート | 提供中 | 提供中 |
data/README.md CHANGED
@@ -99,10 +99,9 @@ System requirements
99
99
  More details about system requirements are available at
100
100
  [Sisimai | Getting Started](https://libsisimai.org/en/start/) page.
101
101
 
102
-
103
102
  * [Ruby 2.4.0 or later](http://www.ruby-lang.org/)
104
103
  * [__oj | The fastest JSON parser and object serializer__](https://rubygems.org/gems/oj)
105
- * Also works on [JRuby 9.0.4.0 - 9.1.17.0](http://jruby.org)
104
+ * Also works on [JRuby 9.2 or later](http://jruby.org)~~
106
105
  * [__jrjackson | A mostly native JRuby wrapper for the java jackson json processor jar__](https://rubygems.org/gems/jrjackson)
107
106
 
108
107
  Install
@@ -110,10 +109,10 @@ Install
110
109
  ### From RubyGems
111
110
  ```shell
112
111
  $ sudo gem install sisimai
113
- Fetching: sisimai-5.0.1.gem (100%)
114
- Successfully installed sisimai-5.0.1
115
- Parsing documentation for sisimai-5.0.1
116
- Installing ri documentation for sisimai-5.0.1
112
+ Fetching: sisimai-5.0.2.gem (100%)
113
+ Successfully installed sisimai-5.0.2
114
+ Parsing documentation for sisimai-5.0.2
115
+ Installing ri documentation for sisimai-5.0.2
117
116
  Done installing documentation for sisimai after 6 seconds
118
117
  1 gem installed
119
118
  ```
@@ -141,13 +140,13 @@ if [ -d "/usr/local/jr" ]; then \
141
140
  ...
142
141
  3 gems installed
143
142
  /opt/local/bin/rake install
144
- sisimai 5.0.0 built to pkg/sisimai-5.0.0.gem.
145
- sisimai (5.0.0) installed.
143
+ sisimai 5.0.2 built to pkg/sisimai-5.0.2.gem.
144
+ sisimai (5.0.2) installed.
146
145
  if [ -d "/usr/local/jr" ]; then \
147
146
  PATH="/usr/local/jr/bin:$PATH" /usr/local/jr/bin/rake install; \
148
147
  fi
149
- sisimai 5.0.0 built to pkg/sisimai-5.0.0-java.gem.
150
- sisimai (5.0.0) installed.
148
+ sisimai 5.0.2 built to pkg/sisimai-5.0.2-java.gem.
149
+ sisimai (5.0.2) installed.
151
150
  ```
152
151
 
153
152
  Usage
@@ -348,15 +347,15 @@ Beginning with v5.0.0, Sisimai requires **Ruby 2.4.0 or later.**
348
347
 
349
348
  | Features | Sisimai 4 | Sisimai 5 |
350
349
  |------------------------------------------------------|--------------------|---------------------|
351
- | System requirements (CRuby) | 2.1 - 3.3.0 | **2.4** - 3.3.0 |
352
- | System requirements (JRuby) | 9.0.4.0 - 9.1.17.0 | 9.0.4.0 - 9.1.17.0 |
350
+ | System requirements (CRuby) | 2.1 - 3.3.0 | **2.4** or later |
351
+ | System requirements (JRuby) | 9.0.4.0 - 9.1.17.0 | **9.2** or later |
353
352
  | Callback feature for the original email file | N/A | Available[^3] |
354
353
  | The number of MTA/ESP modules | 68 | 70 |
355
354
  | The number of detectable bounce reasons | 29 | 34 |
356
355
  | Dependencies (Except Ruby Standard Gems) | 1 gem | 1 gem |
357
- | Source lines of code | 10,300 lines | 11,300 lines |
356
+ | Source lines of code | 10,300 lines | 11,370 lines |
358
357
  | Test frameworks | rspec | minitest |
359
- | The number of tests in spec/ or test/ directory | 311,000 tests | 336,000 tests |
358
+ | The number of tests in spec/ or test/ directory | 311,000 tests | 338,000 tests |
360
359
  | The number of bounce emails decoded/sec (CRuby)[^4] | 231 emails | 305 emails |
361
360
  | License | 2 Clause BSD | 2 Caluse BSD |
362
361
  | Commercial support | Available | Available |
data/lib/sisimai/fact.rb CHANGED
@@ -46,6 +46,18 @@ module Sisimai
46
46
  RFC822Head = Sisimai::RFC5322.HEADERFIELDS(:all)
47
47
  ActionList = { delayed: 1, delivered: 1, expanded: 1, failed: 1, relayed: 1 };
48
48
 
49
+ if RUBY_PLATFORM.start_with?('java')
50
+ # [WORKAROUND] #159 #267 JRuby seems to fail and throws exception at strptime(), but this
51
+ # issue might be fixed in a future version of JRuby.
52
+ # https://gist.github.com/hiroyuki-sato/6ef40245874d4c847a95ef99886e4fa7
53
+ # https://github.com/sisimai/rb-sisimai/issues/267#issuecomment-1976642884
54
+ # https://github.com/jruby/jruby/issues/8139
55
+ # https://github.com/sisimai/rb-sisimai/issues/267
56
+ TimeModule = ::DateTime
57
+ else
58
+ TimeModule = Sisimai::Time
59
+ end
60
+
49
61
  # Constructor of Sisimai::Fact
50
62
  # @param [Hash] argvs Including each parameter
51
63
  # @return [Sisimai::Fact] Structured email data
@@ -188,7 +200,7 @@ module Sisimai
188
200
 
189
201
  begin
190
202
  # Convert from the date string to an object then calculate time zone offset.
191
- t = Sisimai::Time.strptime(datestring, '%a, %d %b %Y %T')
203
+ t = TimeModule.strptime(datestring, '%a, %d %b %Y %T')
192
204
  p['timestamp'] = (t.to_time.to_i - zoneoffset) || nil
193
205
  rescue
194
206
  warn ' ***warning: Failed to strptime ' << datestring.to_s
@@ -196,12 +208,12 @@ module Sisimai
196
208
  next unless p['timestamp']
197
209
 
198
210
  # OTHER_TEXT_HEADERS:
199
- recvheader = mesg1['header']['received'] || []
200
- unless recvheader.empty?
201
- # Get localhost and remote host name from Received header.
202
- %w[lhost rhost].each { |a| e[a] ||= '' }
203
- e['lhost'] = Sisimai::RFC5322.received(recvheader[0]).shift if e['lhost'].empty?
204
- e['rhost'] = Sisimai::RFC5322.received(recvheader[-1]).pop if e['rhost'].empty?
211
+ rr = mesg1['header']['received'] || []
212
+ unless rr.empty?
213
+ # Get a localhost and a remote host name from Received header.
214
+ p['rhost'] = Sisimai::RFC5322.received(rr[-1])[1] if p['rhost'].empty?
215
+ p['lhost'] = '' if p['rhost'] == p['lhost']
216
+ p['lhost'] = Sisimai::RFC5322.received(rr[ 0])[0] if p['lhost'].empty?
205
217
  end
206
218
 
207
219
  # Remove square brackets and curly brackets from the host variable
@@ -339,9 +351,35 @@ module Sisimai
339
351
  o['catch'] = p['catch'] || nil
340
352
  o['hardbounce'] = p['hardbounce']
341
353
  o['replycode'] = Sisimai::SMTP::Reply.find(p['diagnosticcode']).to_s if o['replycode'].empty?
342
- o['timestamp'] = Sisimai::Time.parse(::Time.at(p['timestamp']).to_s)
354
+ o['timestamp'] = TimeModule.parse(::Time.at(p['timestamp']).to_s)
343
355
  o['timezoneoffset'] = p['timezoneoffset'] || '+0000'
344
356
 
357
+ # ALIAS
358
+ while true do
359
+ # Look up the Envelope-To address from the Received: header in the original message
360
+ # when the recipient address is same with the value of o['alias'].
361
+ break if o['alias'].empty?
362
+ break if o['recipient'].address != o['alias']
363
+ break unless rfc822data.has_key?('received')
364
+ break if rfc822data['received'].empty?
365
+
366
+ rfc822data['received'].reverse.each do |er|
367
+ # Search for the string " for " from the Received: header
368
+ next unless er.include?(' for ')
369
+
370
+ af = Sisimai::RFC5322.received(er)
371
+ next if af.empty?
372
+ next if af[5].empty?
373
+ next unless Sisimai::Address.is_emailaddress(af[5])
374
+ next if o['recipient'].address == af[5]
375
+
376
+ o['alias'] = af[5]
377
+ break
378
+ end
379
+ break
380
+ end
381
+ o['alias'] = '' if o['alias'] == o['recipient'].address
382
+
345
383
  # REASON: Decide the reason of email bounce
346
384
  if o['reason'].empty? || RetryIndex[o['reason']]
347
385
  # The value of "reason" is empty or is needed to check with other values again
@@ -305,7 +305,6 @@ module Sisimai::Lhost
305
305
 
306
306
  dscontents.each do |e|
307
307
  # Set default values if each value is empty.
308
- e['lhost'] ||= permessage['rhost']
309
308
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
310
309
 
311
310
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].to_s.tr("\n", ' '))
@@ -98,7 +98,6 @@ module Sisimai::Lhost
98
98
 
99
99
  dscontents.each do |e|
100
100
  # Set default values if each value is empty.
101
- e['lhost'] ||= permessage['rhost']
102
101
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
103
102
 
104
103
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
@@ -100,7 +100,6 @@ module Sisimai::Lhost
100
100
 
101
101
  dscontents.each do |e|
102
102
  # Set default values if each value is empty.
103
- e['lhost'] ||= permessage['rhost']
104
103
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
105
104
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].tr("\n", ' '))
106
105
 
@@ -108,7 +108,6 @@ module Sisimai::Lhost
108
108
 
109
109
  dscontents.each do |e|
110
110
  # Set default values if each value is empty.
111
- e['lhost'] ||= permessage['rhost']
112
111
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
113
112
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
114
113
  e['command'] = thecommand || ''
@@ -123,7 +123,6 @@ module Sisimai::Lhost
123
123
  dscontents.each do |e|
124
124
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
125
125
  e['recipient'] = Sisimai::Address.s3s4(e['recipient'])
126
- e['lhost'] ||= permessage['rhost']
127
126
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
128
127
 
129
128
  MessagesOf.each_key do |r|
@@ -147,7 +147,7 @@ module Sisimai::Lhost
147
147
  next unless p > 0
148
148
 
149
149
  # #550 5.1.1 RESOLVER.ADR.RecipNotFound; not found ##
150
- f = e['diagnosis'][p + 1, e['diagnosis'].index(';') - p -1]
150
+ f = e['diagnosis'][p + 1, e['diagnosis'].index(';') - p - 1]
151
151
  NDRSubject.each_key do |r|
152
152
  # Try to match with error subject strings
153
153
  next unless f == r
@@ -147,7 +147,6 @@ module Sisimai::Lhost
147
147
  readcursor = 0 # (Integer) Points the current cursor position
148
148
  nextcursor = 0
149
149
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
150
- localhost0 = '' # (String) Local MTA
151
150
  boundary00 = '' # (String) Boundary string
152
151
  v = nil
153
152
 
@@ -342,18 +341,13 @@ module Sisimai::Lhost
342
341
  end
343
342
  return nil unless recipients > 0
344
343
 
345
- unless mhead['received'].empty?
346
- # Get the name of local MTA
347
- # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128])
348
- p1 = mhead['received'][-1].index('from ') || -1
349
- p2 = mhead['received'][-1].index(' ', p1 + 5) || -1
350
- localhost0 = mhead['received'][-1][p1 + 5, p2 - p1 - 5] if p1 > -1 && p2 > -1
351
- end
344
+ # Get the name of the local MTA
345
+ # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128])
346
+ receivedby = mhead['received'] || []
347
+ recvdtoken = Sisimai::RFC5322.received(receivedby[-1])
352
348
 
353
349
  dscontents.each do |e|
354
- # Set default values if each value is empty.
355
- e['lhost'] ||= localhost0
356
-
350
+ # Check the error message, the rhost, the lhost, and the smtp command.
357
351
  unless e['diagnosis']
358
352
  # Empty Diagnostic-Code: or error message
359
353
  unless boundary00.empty?
@@ -405,12 +399,9 @@ module Sisimai::Lhost
405
399
  p1 = e['diagnosis'].index('host ') || -1
406
400
  p2 = e['diagnosis'].index(' ', p1 + 5) || -1
407
401
  e['rhost'] = e['diagnosis'][p1 + 5, p2 - p1 - 5] if p1 > -1
408
-
409
- unless e['rhost']
410
- # Get localhost and remote host name from Received header.
411
- e['rhost'] = Sisimai::RFC5322.received(mhead['received'][-1]).pop unless mhead['received'].empty?
412
- end
402
+ e['rhost'] ||= recvdtoken[1]
413
403
  end
404
+ e['lhost'] ||= recvdtoken[0]
414
405
 
415
406
  unless e['command']
416
407
  # Get the SMTP command name for the session
@@ -146,7 +146,6 @@ module Sisimai::Lhost
146
146
  return nil unless recipients > 0
147
147
 
148
148
  dscontents.each do |e|
149
- e['lhost'] ||= permessage['lhost']
150
149
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
151
150
 
152
151
  p0 = e['diagnosis'].index('-') || -1
@@ -48,9 +48,10 @@ module Sisimai::Lhost
48
48
  # * You may need to join the group before receiving permission to post.
49
49
  # * This group may not be open to posting.
50
50
  entiremesg = emailparts[0].split(/\n\n/, 5).slice(0, 4).join(' ').tr("\n", ' ');
51
+ receivedby = mhead['received'] || []
51
52
  recordwide['diagnosis'] = Sisimai::String.sweep(entiremesg)
52
53
  recordwide['reason'] = emailparts[0].scan(/^[ ]?[*][ ]?/).size == 4 ? 'rejected' : 'onhold'
53
- recordwide['rhost'] = Sisimai::RFC5322.received(mhead['received'][0]).shift
54
+ recordwide['rhost'] = Sisimai::RFC5322.received(receivedby[0])[1]
54
55
 
55
56
  mhead['x-failed-recipients'].split(',').each do |e|
56
57
  # X-Failed-Recipients: neko@example.jp, nyaan@example.org, ...
@@ -135,7 +135,6 @@ module Sisimai::Lhost
135
135
 
136
136
  dscontents.each do |e|
137
137
  # Set default values if each value is empty.
138
- e['lhost'] ||= permessage['rhost']
139
138
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
140
139
 
141
140
  if anotherset['diagnosis']
@@ -69,7 +69,6 @@ module Sisimai::Lhost
69
69
  bodyslices = emailparts[0].split("\n")
70
70
  readcursor = 0 # (Integer) Points the current cursor position
71
71
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
72
- localhost0 = '' # (String) Local MTA
73
72
  v = nil
74
73
 
75
74
  while e = bodyslices.shift do
@@ -145,18 +144,13 @@ module Sisimai::Lhost
145
144
  end
146
145
  return nil unless recipients > 0
147
146
 
148
- unless mhead['received'].empty?
149
- # Get the name of local MTA
150
- # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128])
151
- p1 = mhead['received'][-1].index('from ') || -1
152
- p2 = mhead['received'][-1].index(' ', p1 + 5) || -1
153
- localhost0 = mhead['received'][-1][p1 + 5, p2 - p1 - 5] if p1 > -1
154
- end
147
+ # Get the name of the local MTA
148
+ # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128])
149
+ receivedby = mhead['received'] || []
150
+ recvdtoken = Sisimai::RFC5322.received(receivedby[-1])
155
151
 
156
152
  dscontents.each do |e|
157
- # Set default values if each value is empty.
158
- e['lhost'] ||= localhost0
159
-
153
+ # Check the error message, the rhost, the lhost, and the smtp command.
160
154
  unless e['alterrors'].to_s.empty?
161
155
  # Copy alternative error message
162
156
  e['diagnosis'] ||= e['alterrors']
@@ -175,13 +169,10 @@ module Sisimai::Lhost
175
169
  # host neko.example.jp [192.0.2.222]: 550 5.1.1 <kijitora@example.jp>... User Unknown
176
170
  p1 = e['diagnosis'].index('host ') || -1
177
171
  p2 = e['diagnosis'].index(' ', p1 + 5) || -1
178
- e['rhost'] = e['diagnosis'][p1 + 5, p2 - p1 - 5] if p1 > -1
179
-
180
- unless e['rhost']
181
- # Get localhost and remote host name from Received header.
182
- e['rhost'] = Sisimai::RFC5322.received(mhead['received'][-1]).pop unless mhead['received'].empty?
183
- end
172
+ e['rhost'] = e['diagnosis'][p1 + 5, p2 - p1 - 5] if p1 > -1
173
+ e['rhost'] ||= recvdtoken[1]
184
174
  end
175
+ e['lhost'] ||= recvdtoken[0]
185
176
 
186
177
  unless e['command']
187
178
  # Get the SMTP command name for the session
@@ -98,7 +98,6 @@ module Sisimai::Lhost
98
98
 
99
99
  dscontents.each do |e|
100
100
  # Set default values if each value is empty.
101
- e['lhost'] ||= permessage['rhost']
102
101
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
103
102
  e['command'] = commandset.shift || ''
104
103
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
@@ -104,7 +104,7 @@ module Sisimai::Lhost
104
104
  rhosts = Sisimai::RFC5322.received(rheads[-1])
105
105
 
106
106
  e['lhost'] ||= Sisimai::RFC5322.received(rheads[0]).shift
107
- while ee = rhosts.shift do
107
+ [rhosts[0], rhosts[1]].each do |ee|
108
108
  # Avoid "... by m-FILTER"
109
109
  next unless ee.include?('.')
110
110
  e['rhost'] = ee
@@ -89,7 +89,6 @@ module Sisimai::Lhost
89
89
  bodyslices = emailparts[0].split("\n")
90
90
  readcursor = 0 # (Integer) Points the current cursor position
91
91
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
92
- localhost0 = '' # (String) Local MTA
93
92
  v = nil
94
93
 
95
94
  while e = bodyslices.shift do
@@ -135,19 +134,14 @@ module Sisimai::Lhost
135
134
  end
136
135
  return nil unless recipients > 0
137
136
 
138
- unless mhead['received'].empty?
139
- # Get the name of local MTA
140
- p1 = mhead['received'][-1].downcase.index('from ')
141
- p2 = mhead['received'][-1].index(' ', p1 + 5)
142
-
143
- if (p1 + 1) * (p2 + 1) > 0
144
- # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128])
145
- localhost0 = mhead['received'][-1][p1 + 5, p2 - p1 - 5]
146
- end
147
- end
137
+ # Get the name of the local MTA
138
+ # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128])
139
+ receivedby = mhead['received'] || []
140
+ recvdtoken = Sisimai::RFC5322.received(receivedby[-1])
148
141
 
149
142
  dscontents.each do |e|
150
- e['lhost'] = localhost0
143
+ # Check the error message, the rhost, the lhost, and the smtp command.
144
+ e['lhost'] = recvdtoken[0]
151
145
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].gsub(/[-]{2}.*\z/, ''))
152
146
 
153
147
  unless e['rhost']
@@ -156,12 +150,8 @@ module Sisimai::Lhost
156
150
  p2 = e['diagnosis'].index(' ', p1 + 5)
157
151
 
158
152
  # host neko.example.jp [192.0.2.222]: 550 5.1.1 <kijitora@example.jp>... User Unknown
159
- e['rhost'] = e['diagnosis'][p1 + 5, p2 - p1 - 5] if p1 > -1
160
-
161
- unless e['rhost']
162
- # Get localhost and remote host name from Received header.
163
- e['rhost'] = Sisimai::RFC5322.received(mhead['received'][-1]).pop unless mhead['received'].empty?
164
- end
153
+ e['rhost'] = e['diagnosis'][p1 + 5, p2 - p1 - 5] if p1 > -1
154
+ e['rhost'] ||= recvdtoken[1]
165
155
  end
166
156
 
167
157
  unless e['command']
@@ -70,7 +70,7 @@ module Sisimai::Lhost
70
70
  %r/\A5[.]7[.]1[23]\z/ => 'rejected',
71
71
  %r/\A5[.]7[.]124\z/ => 'rejected',
72
72
  %r/\A5[.]7[.]13[3-6]\z/ => 'rejected',
73
- %r/\A5[.]7[.]23\z/ => 'blocked',
73
+ %r/\A5[.]7[.]23\z/ => 'authfailure',
74
74
  %r/\A5[.]7[.]25\z/ => 'networkerror',
75
75
  %r/\A5[.]7[.]50[1-3]\z/ => 'spamdetected',
76
76
  %r/\A5[.]7[.]50[4-5]\z/ => 'filtered',
@@ -97,7 +97,6 @@ module Sisimai::Lhost
97
97
 
98
98
  dscontents.each do |e|
99
99
  # Set default values if each value is empty.
100
- e['lhost'] ||= permessage['rhost']
101
100
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
102
101
 
103
102
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
@@ -227,7 +227,6 @@ module Sisimai::Lhost
227
227
 
228
228
  dscontents.each do |e|
229
229
  # Set default values if each value is empty.
230
- e['lhost'] ||= permessage['rhost']
231
230
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
232
231
 
233
232
  if anotherset['diagnosis']
@@ -96,7 +96,6 @@ module Sisimai::Lhost
96
96
 
97
97
  dscontents.each do |e|
98
98
  # Set default values if each value is empty.
99
- e['lhost'] ||= permessage['rhost']
100
99
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
101
100
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].tr("\n", ' '))
102
101
 
@@ -124,6 +124,7 @@ module Sisimai::Lhost
124
124
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
125
125
  e['replycode'] = Sisimai::SMTP::Reply.find(e['diagnosis']) || ''
126
126
  e['status'] = e['replycode'][0, 1] + '.0.0' if e['replycode'].size == 3
127
+ e['command'] = thecommand
127
128
 
128
129
  if e['status'] == '5.0.0' || e['status'] == '4.0.0'
129
130
  # Get the value of D.S.N. from the error message or the value of Diagnostic-Code header.
@@ -138,9 +139,6 @@ module Sisimai::Lhost
138
139
  e['status'] = Sisimai::SMTP::Status.code('expired') || e['status']
139
140
  end
140
141
  end
141
-
142
- e['lhost'] ||= permessage['rhost']
143
- e['command'] = thecommand
144
142
  end
145
143
 
146
144
  return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
@@ -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?
@@ -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',
@@ -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
@@ -7,6 +7,7 @@ module Sisimai
7
7
  class << self
8
8
  MessagesOf = {
9
9
  # https://community.mimecast.com/s/article/Mimecast-SMTP-Error-Codes-842605754
10
+ # https://community.mimecast.com/s/article/email-security-cloud-gateway-mimecast-smtp-error-codes
10
11
  'authfailure' => [
11
12
  # - The inbound message has been rejected because the originated IP address isn't list-
12
13
  # ed in the published SPF records for the sending domain.
@@ -76,6 +77,12 @@ module Sisimai
76
77
  [554, 'maximum email size exceeded'],
77
78
  ],
78
79
  'networkerror' => [
80
+ # - The recipients' domains have MX records configured incorrectly
81
+ # - Check and remove any MX records that point to hostnames with outbound references.
82
+ # Only Inbound smart hosts are supported on MX records.
83
+ [451, 'the incorrect hostname used for inbounds'],
84
+ [550, 'the incorrect hostname used for inbounds'],
85
+
79
86
  # - The message has too many "received headers" as it has been forwarded across multi-
80
87
  # ple hops. Once 25 hops have been reached, the email is rejected.
81
88
  # - Investigate the email addresses in the communication pairs, to see what forwarders
@@ -135,8 +142,8 @@ module Sisimai
135
142
 
136
143
  # - A personal block policy is in place for the email address/domain.
137
144
  # - Remove the email address/domain from the Managed Senders list.
138
- [550, 'envelope blocked user entry'],
139
- [550, 'envelope blocked user domain entry'],
145
+ [550, 'envelope blocked - user entry'],
146
+ [550, 'envelope blocked - user domain entry'],
140
147
  [550, 'rejected by header-based manually blocked senders - block for manual block'],
141
148
 
142
149
  # - A Block Sender Policy has been applied to reject emails based on the Header From or
@@ -1,4 +1,4 @@
1
1
  # Define the version number of Sisimai
2
2
  module Sisimai
3
- VERSION = '5.0.1'.freeze
3
+ VERSION = '5.0.2'.freeze
4
4
  end
@@ -0,0 +1,85 @@
1
+ Return-Path: <>
2
+ X-Original-To: nekochan@email.example.jp
3
+ Delivered-To: mail@email.example.jp
4
+ Received: from mx311.ume.example.ne.jp (ip-192-0-2-25.us-east-1.compute.internal [192.0.2.25])
5
+ by mx1.email.example.jp (Postfix) with ESMTPS id Ln2ZS7LPwxzW4HMF
6
+ for <nekochan@email.example.jp>; Wed, 7 Feb 2024 23:34:45 +0900 (JST)
7
+ Received: from localhost (localhost)
8
+ by mx311.ume.example.ne.jp (8.16.1/8.16.1) id 3LGub1et091679;
9
+ Wed, 7 Feb 2024 23:34:45 +0900 (JST)
10
+ (envelope-from MAILER-DAEMON)
11
+ Date: Wed, 7 Feb 2024 23:34:45 +0900 (JST)
12
+ From: Mail Delivery Subsystem <MAILER-DAEMON@mx311.ume.example.ne.jp>
13
+ To: <nekochan@email.example.jp>
14
+ Message-Id: <202402072222.3LGub1et091679@mx311.ume.example.ne.jp>
15
+ MIME-Version: 1.0
16
+ Content-Type: multipart/report; report-type=delivery-status;
17
+ boundary="3LGub1et091679.1157497350/mx311.ume.example.ne.jp"
18
+ Subject: Returned mail: see transcript for details
19
+ Auto-Submitted: auto-generated (failure)
20
+
21
+ This is a MIME-encapsulated message
22
+
23
+ --3LGub1et091679.1157497350/mx311.ume.example.ne.jp
24
+
25
+ The original message was received at Wed, 7 Feb 2024 23:34:45 +0900 (JST)
26
+ from localhost [127.0.0.1]
27
+
28
+ ----- The following addresses had permanent fatal errors -----
29
+ <kijitora-cat@google.example.com>
30
+ (reason: 550-5.7.26 The MAIL FROM domain [email.example.jp] has an SPF record with a hard fail)
31
+
32
+ ----- Transcript of session follows -----
33
+ ... while talking to gmail-smtp-in.l.google.com.:
34
+ >>> DATA
35
+ <<< 550-5.7.26 The MAIL FROM domain [email.example.jp] has an SPF record with a hard fail
36
+ <<< 550-5.7.26 policy (-all) but it fails to pass SPF checks with the ip:
37
+ <<< 550-5.7.26 [203.0.113.22]. To best protect our users from spam and phishing,
38
+ <<< 550-5.7.26 the message has been blocked. For instructions on setting up
39
+ <<< 550-5.7.26 authentication, go to
40
+ <<< 550 5.7.26 https://support.google.com/mail/answer/81126#authentication q2.22 - gsmtp
41
+ 554 5.0.0 Service unavailable
42
+
43
+ --3LGub1et091679.1157497350/mx311.ume.example.ne.jp
44
+ Content-Type: message/delivery-status
45
+
46
+ Reporting-MTA: dns; mx311.ume.example.ne.jp
47
+ Arrival-Date: Wed, 7 Feb 2024 23:34:45 +0900 (JST)
48
+
49
+ Final-Recipient: RFC822; kijitora-cat@google.example.com
50
+ X-Actual-Recipient: rfc822; kijitora-cat@google.example.com
51
+ Action: failed
52
+ Status: 5.7.26
53
+ Remote-MTA: DNS; gmail-smtp-in.l.google.com
54
+ Diagnostic-Code: SMTP; 550-5.7.26 The MAIL FROM domain [email.example.jp] has an SPF record with a hard fail
55
+ Last-Attempt-Date: Wed, 7 Feb 2024 23:34:45 +0900 (JST)
56
+
57
+ --3LGub1et091679.1157497350/mx311.ume.example.ne.jp
58
+ Content-Type: message/rfc822
59
+
60
+ Return-Path: <nekochan@email.example.jp>
61
+ Received: from mx311.ume.example.ne.jp (localhost [127.0.0.1])
62
+ by mx311.ume.example.ne.jp (8.16.1/8.16.1) with ESMTP id ujmeV2ZG033926
63
+ for <kijitora-cat@google.example.com>; Wed, 7 Feb 2024 23:34:45 +0900 (JST)
64
+ (envelope-from nekochan@email.example.jp)
65
+ Received: (from kijitora@localhost)
66
+ by mx311.ume.example.ne.jp (8.16.1/8.16.1/Submit) id BJObadmZ060489
67
+ for kijitora-cat@google.example.com; Wed, 7 Feb 2024 23:34:45 +0900 (JST)
68
+ (envelope-from nekochan@email.example.jp)
69
+ Received: from relay1.email.example.jp (relay1.email.example.jp [192.168.168.168])
70
+ by mx311.ume.example.ne.jp (8.16.1/8.16.1) with ESMTPS id Mf73VCB9P8z4lM3b
71
+ (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO)
72
+ for <neko@libsisimai.org>; Wed, 7 Feb 2024 23:34:45 +0900 (JST)
73
+ (envelope-from nekochan@email.example.jp)
74
+ Content-Type: text/plain; charset=us-ascii
75
+ Message-Id: <50ad190.20240207.2202@relay1.email.example.jp>
76
+ Content-Transfer-Encoding: 7bit
77
+ Subject: Nyaan
78
+ From: <nekochan@email.example.jp>
79
+ To: <neko@libsisimai.org>
80
+ Date: 7 Feb 2024 23:34:45 +0900
81
+ MIME-Version: 1.0
82
+
83
+ Nyaan?
84
+
85
+ --3LGub1et091679.1157497350/mx311.ume.example.ne.jp--
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sisimai
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.1
4
+ version: 5.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - azumakuniyuki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-03 00:00:00.000000000 Z
11
+ date: 2024-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -74,6 +74,7 @@ executables: []
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - ".github/workflows/rake-test.yml"
77
78
  - ".gitignore"
78
79
  - ".rubocop.yml"
79
80
  - ".travis.yml"
@@ -640,6 +641,7 @@ files:
640
641
  - set-of-emails/maildir/bsd/lhost-sendmail-57.eml
641
642
  - set-of-emails/maildir/bsd/lhost-sendmail-58.eml
642
643
  - set-of-emails/maildir/bsd/lhost-sendmail-59.eml
644
+ - set-of-emails/maildir/bsd/lhost-sendmail-60.eml
643
645
  - set-of-emails/maildir/bsd/lhost-surfcontrol-01.eml
644
646
  - set-of-emails/maildir/bsd/lhost-surfcontrol-02.eml
645
647
  - set-of-emails/maildir/bsd/lhost-surfcontrol-03.eml