Skip to content

Latest commit

 

History

History
466 lines (395 loc) · 24.5 KB

README-JA.md

File metadata and controls

466 lines (395 loc) · 24.5 KB

License Ruby Gem Version codecov

Important

2024年2月2日の時点でこのリポジトリのデフォルトブランチは5-stable (Sisimai 5)になりました。 もし古いバージョンを使いたい場合は4-stable1 ブランチを見てください。またmainmasterブランチはもうこのリポジトリでは使用していません。

Caution

Sisimai 4.25.14p11およびそれ以前のバージョンには 正規表現に関する脆弱性 ReDoS: CVE-2022-4891があります。 該当するバージョンをお使いの場合はv4.25.14p12以降へアップグレードしてください。

Warning

Sisimai 5はRuby 2.4以上が必要です。インストール/アップグレードを実行する前にruby -vコマンドで システムに入っているRubyのバージョンを確認してください。

Note

SisimaiはPerlモジュールまたはRuby Gemですが、PHPやPython、GoやRustなどJSONを読める言語であれば どのような環境においても解析結果を得ることでバウンスの発生状況を捉えるのにとても有用です。

What is Sisimai

Sisimai(シシマイ)は複雑で多種多様なバウンスメールを解析してバウンスした理由や宛先メールアドレスなど 配信が失敗した結果を構造化データで出力するライブラリでJSONでの出力も可能です。Ruby版シシマイは github.com/sisimai/p5-sisimaiで公開しているPerl版シシマイ から移植しました。

The key features of Sisimai

  • バウンスメールを構造化したデータに変換
    • 以下24項目の情報を含むデータ構造2
      • 基本的情報: timestamp, origin
      • 発信者情報: addresser, senderdomain,
      • 受信者情報: recipient, destination, alias
      • 配信の情報: action, replycode,action, replycode, deliverystatus
      • エラー情報: reason, diagnosticcode, diagnostictype, feedbacktype, hardbounce
      • メール情報: subject, messageid, listid,
      • その他情報: smtpagent, timezoneoffset, lhost, rhost, token, catch
    • 出力可能な形式
      • Ruby (Hash, Array)
      • JSON
      • YAML (yamlが必要)
    • インストールも使用も簡単
      • gem install
      • git clone && make
    • 高い解析精度

Command line demo

次の画像のように、Ruby版シシマイ(rb-sisimai)はコマンドラインから簡単にバウンスメールを解析すること ができます。

Setting Up Sisimai

System requirements

Sisimaiの動作環境についての詳細はSisimai | シシマイを使ってみる をご覧ください。

Install

From RubyGems.org

$ sudo gem install sisimai
Fetching: sisimai-5.1.0.gem (100%)
Successfully installed sisimai-5.1.0
Parsing documentation for sisimai-5.1.0
Installing ri documentation for sisimai-5.1.0
Done installing documentation for sisimai after 6 seconds
1 gem installed

From GitHub

Warning

Sisimai 5はRuby 2.4以上が必要です。インストール/アップグレードを実行する前にruby -vコマンドで システムに入っているRubyのバージョンを確認してください。

% ruby -v
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin21]

$ cd /usr/local/src
$ git clone https://github.com/sisimai/rb-sisimai.git

$ cd ./rb-sisimai
$ sudo make depend install-from-local
gem install bundle rake minitest
...
3 gems installed
if [ -d "/usr/local/jr" ]; then \
		PATH="/usr/local/jr/bin:$PATH" /usr/local/jr/bin/gem install bundle rake minitest; \
	fi
...
3 gems installed
/opt/local/bin/rake install
sisimai 5.1.0 built to pkg/sisimai-5.1.0.gem.
sisimai (5.1.0) installed.
if [ -d "/usr/local/jr" ]; then \
		PATH="/usr/local/jr/bin:$PATH" /usr/local/jr/bin/rake install; \
	fi
sisimai 5.1.0 built to pkg/sisimai-5.1.0-java.gem.
sisimai (5.1.0) installed.

Usage

Basic usage

下記のようにSisimaiのrise()メソッドをmboxかMaildirのPATHを引数にして実行すると解析結果が配列で 返ってきます。v4.25.6から元データとなった電子メールファイルへのPATHを保持するoriginが利用できます。

#! /usr/bin/env ruby
require 'sisimai'
v = Sisimai.rise('/path/to/mbox')       # またはMaildir/へのPATH

# v4.23.0からSisimaiクラスのrise()メソッドとdump()メソッドはPATH以外にもバウンスメール全体を文字列
# として読めるようになりました
f = File.open('/path/to/mbox', 'r');    # またはMaildir/へのPATH
v = Sisimai.rise(f.read)

# もし"delivered"(配信成功)となる解析結果も必要な場合は以下に示すとおりrise()メソッドに"delivered"
# オプションを指定してください
v = Sisimai.rise('/path/to/mbox', delivered: true)

# v5.0.0からSisimaiはバウンス理由が"vacation"となる解析結果をデフォルトで返さなくなりました。もし
# "vacation"となる解析結果も必要な場合は次のコードで示すようにrise()メソッドに"vacation"オプション
# を指定してください。
v = Sisimai.rise('/path/to/mbox', vacation: true);

if v.is_a? Array
  v.each do |e|
    puts e.class                # Sisimai::Fact
    puts e.recipient.class      # Sisimai::Address
    puts e.timestamp.class      # Sisimai::Time

    puts e.addresser.address    # "[email protected]" # From
    puts e.recipient.address    # "[email protected]"    # To
    puts e.recipient.host       # "example.jp"
    puts e.deliverystatus       # "5.1.1"
    puts e.replycode            # "550"
    puts e.reason               # "userunknown"
    puts e.origin               # "/var/spool/bounce/Maildir/new/1740074341.eml"
    puts e.hardbounce           # true

    h = e.damn                  # Hashに変換
    j = e.dump('json')          # JSON(文字列)に変換
    puts e.dump('json')         # JSON化したバウンスメールの解析結果を表示
  end
end

Convert to JSON

下記のようにSisimaiのdump()メソッドをmboxかMaildirのPATHを引数にして実行すると解析結果が文字列 (JSON)で返ってきます。

# メールボックスまたはMaildir/から解析した結果をJSONにする
puts Sisimai.dump('/path/to/mbox')  # またはMaildir/へのPATH

# dump()メソッドは"delivered"オプションや"vacation"オプションも指定可能
puts Sisimai.dump('/path/to/mbox', delivered: true, vacation: true)

Callback feature

Sisimai.riseSisimai.dump:c___引数(c_が三個/魚用の釣り針に見える)はコールバック機能 で呼び出されるProcオブジェクトを保持する配列です。:c___の1番目の要素にはSisimai::Message.sift で呼び出されるProcオブジェクトでメールヘッダと本文に対して行う処理を、2番目の要素には、解析対象の メールファイルに対して行う処理をそれぞれ入れます。

各Procオブジェクトで処理した結果はSisimai::Fact.catchを通して得られます。

[0] メールヘッダと本文に対して

:c___に渡す配列の最初の要素に入れたProcオブジェクトはSisimai::Message->parse()で呼び出されます。

#! /usr/bin/env ruby
require 'sisimai'
code = lambda do |args|
  head = args['headers']    # (*Hash)  メールヘッダー
  body = args['message']    # (String) メールの本文
  adds = { 'x-mailer' => '', 'queue-id' => '' }

  if cv = body.match(/^X-Postfix-Queue-ID:\s*(.+)$/)
    adds['queue-id'] = cv[1]
  end
  r['x-mailer'] = head['x-mailer'] || ''
  return adds
end

data = Sisimai.rise('/path/to/mbox', c___: [code, nil])
json = Sisimai.dump('/path/to/mbox', c___: [code, nil])

puts data[0].catch['x-mailer']  # "Apple Mail (2.1283)"
puts data[0].catch['queue-id']  # "43f4KX6WR7z1xcMG"

各メールのファイルに対して

Sisimai.riseSisimai.dumpの両メソッドに渡せる引数:c___(配列)の2番目に入れたProcオブジェクト は解析したメールのファイルごとに呼び出されます。

path = '/path/to/maildir'
code = lambda do |args|
  kind = args['kind']   # (String) Sisimai::Mail.kind
  mail = args['mail']   # (String) Entire email message
  path = args['path']   # (String) Sisimai::Mail.path
  fact = args['fact']   # (Array)  List of Sisimai::Fact

  fact.each do |e|
    # "catch"アクセサの中に独自の情報を保存する
    e.catch ||= {}
    e.catch['size'] = mail.size
    e.catch['kind'] = kind.capitalize

    if cv = mail.match(/^Return-Path: (.+)$/)
      # Return-Path: <MAILER-DAEMON>
      e.catch['return-path'] = cv[1]
    end
    e.catch['parsedat'] = Time.new.localtime.to_s

    # "X-Sisimai-Parsed:"ヘッダーを追加して別のPATHに元メールを保存する
    a = sprintf("X-Sisimai-Parsed: %d", fact.size)
    p = sprintf("/path/to/another/directory/sisimai-%s.eml", e.token)
    v = mail.sub(/^(From:.+?)$/, '\1' + "\n" + a)
    f = File.open(p, 'w:UTF-8')
    f.write(v)
    f.close

    # 解析が終わったらMaildir/にあるファイルを削除する
    File.delete(path) if kind == 'maildir'

    # 特に何か値をReturnする必要はない
  end
end

list = Sisimai.rise(path, c___: [nil, code])

puts list[0].catch['size']          # 2202
puts list[0].catch['kind']          # "Maildir"
puts list[0].catch['return-path']   # "<MAILER-DAEMON>"

コールバック機能のより詳細な使い方は Sisimai | 解析方法 - コールバック機能をご覧ください。

One-Liner

$ ruby -rsisimai -e 'puts Sisimai.dump($*.shift)' /path/to/mbox

Output example

[
  {
    "destination": "google.example.com",
    "lhost": "gmail-smtp-in.l.google.com",
    "hardbounce": 0,
    "reason": "authfailure",
    "catch": null,
    "addresser": "[email protected]",
    "alias": "[email protected]",
    "smtpagent": "Postfix",
    "smtpcommand": "DATA",
    "senderdomain": "example.jp",
    "listid": "",
    "action": "failed",
    "feedbacktype": "",
    "messageid": "[email protected]",
    "origin": "./gmail-5.7.26.eml",
    "recipient": "[email protected]",
    "rhost": "gmail-smtp-in.l.google.com",
    "subject": "Nyaan",
    "timezoneoffset": "+0900",
    "replycode": 550,
    "token": "84656774898baa90660be3e12fe0526e108d4473",
    "diagnostictype": "SMTP",
    "timestamp": 1650119685,
    "diagnosticcode": "host gmail-smtp-in.l.google.com[64.233.187.27] said: This mail has been blocked because the sender is unauthenticated. Gmail requires all senders to authenticate with either SPF or DKIM. Authentication results: DKIM = did not pass SPF [relay3.example.com] with ip: [192.0.2.22] = did not pass For instructions on setting up authentication, go to https://support.google.com/mail/answer/81126#authentication c2-202200202020202020222222cat.127 - gsmtp (in reply to end of DATA command)",
    "deliverystatus": "5.7.26"
  }
]

Differences between Sisimai 4 and Sisimai 5

Sisimai 4.25.16p1Sisimai 5には下記のような違いがあります。 それぞれの詳細はSisimai | 違いの一覧を参照してください。

Features

Sisimai 5.0.0からRuby 2.4以上が必要になります。

機能 Sisimai 4 Sisimai 5
動作環境(CRuby) 2.1 - 2.4 or later
動作環境(JRuby) 9.0.4.0 - 9.1.17.0 9.2 or later
元メールファイルを操作可能なコールバック機能 なし あり3
解析エンジン(MTA/ESPモジュール)の数 68 73
検出可能なバウンス理由の数 29 34
依存Gem数(Ruby Standard Gemsを除く) 1 Gem 1 Gem
ソースコードの行数 10,800 行 11,660 行
テストフレームワーク rspec minitest
テスト件数(spec/またはtest/ディレクトリ) 311,000 件 414,000 件
1秒間に解析できるバウンスメール数4 231 通 305 通
ライセンス 2条項BSD 2条項BSD
開発会社による商用サポート 提供中 提供中

Decoding Method

いくつかの解析メソッド名、クラス名、パラメーター名がSisimai 5で変更になっています。解析済みデータの 各項目はLIBSISIMAI.ORG/JA/DATAを参照してください。

解析用メソッド周辺の変更箇所 Sisimai 4 Sisimai 5
解析メソッド名 Sisimai.make Sisimai.rise
出力メソッド名 Sisimai.dump Sisimai.dump
解析メソッドが返すオブジェクトのクラス Sisimai::Data Sisimai::Fact
コールバック用のパラメーター名 hook c___5
ハードバウンスかソフトバウンスかを識別するメソッド名 softbounce hardbounce
"vacation"をデフォルトで検出するかどうか 検出する 検出しない
Sisimai::Messageがオブジェクトを返すかどうか 返す 返さない
MIME解析用クラスの名前 Sisimai::MIME Sisimai::RFC2045
SMTPセッションの解析をするかどうか しない する6

MTA/ESP Module Names

Sisimai 5で3個のESPモジュール名(解析エンジン)が変更になりました。詳細はMTA/ESPモジュールの一覧/ LIBSISIMAI.ORG/JA/ENGINEを参照してください。

Sisimai:: Sisimai 4 Sisimai 5
Apple iCloud Mail (added at v5.1.0) なし Rhost::Apple
Microsoft Exchange Online Rhost::ExchangeOnline Rhost::Microsoft
Google Workspace Rhost::GoogleApps Rhost::Google
Tencent Rhost::TencentQQ Rhost::Tencent
Yahoo Mail (added at v5.1.0) なし Rhost::YahooInc
DragonFly Mail Agent (added at v5.1.0) なし Lhost::DragonFly

Bounce Reasons

Sisimai 5では新たに5個のバウンス理由が増えました。検出可能なバウンス理由の一覧は LIBSISIMAI.ORG/JA/REASONを参照してください。

バウンスした理由 Sisimai 4 Sisimai 5
ドメイン認証によるもの(SPF,DKIM,DMARC) SecurityError AuthFailure
送信者のドメイン・IPアドレスの低いレピュテーション Blocked BadReputation
PTRレコードが未設定または無効なPTRレコード Blocked RequirePTR
RFCに準拠していないメール7 SecurityError NotCompliantRFC
単位時間の流量制限・送信速度が速すぎる SecurityError Speeding

Contributing

Bug report

もしもSisimaiにバグを発見した場合はIssuesにて連絡を いただけると助かります。

Emails could not be decoded

Sisimaiで解析できないバウンスメールは set-of-emails/to-be-debugged-because/sisimai-cannot-parse-yetリポジトリに追加してPull-Requestを送ってください。

Other Information

Related sites

See also

Author

@azumakuniyuki

Copyright

Copyright (C) 2015-2024 azumakuniyuki, All Rights Reserved.

License

This software is distributed under The BSD 2-Clause License.

Footnotes

  1. 4系をcloneする場合はgit clone -b 4-stable https://github.com/sisimai/rb-sisimai.git

  2. コールバック機能を使用するとcatchアクセサの下に独自のデータを追加できます

  3. Sisimai.riseメソッドで指定する:c___パラメーター第二引数で指定可能

  4. macOS Monterey/1.6GHz Dual-Core Intel Core i5/16GB-RAM/Ruby 3.3.0

  5. :c___は漁港で使う釣り針に見える

  6. Sisimai::SMTP::Transcript.riseメソッドによる

  7. RFC5322など