株式会社クリアコード > ククログ

ククログ

クリアコードはプログラミングが好きなソフトウェア開発者を2名募集しています。

クリアコードはフリーソフトウェア開発で培った技術力を提供しています。特にMozilla製品(Mozilla FirefoxとMozilla Thunderbird)Rubyに関連した開発を得意としています。

Ohloh profile for kou RubyKaigi2010 Sponsor RubyKaigi2010 Speaker RubyKaigi2010 Committer
最新
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|
タグ:

Cutter 1.1.3リリース

C・C++言語用の単体テストフレームワークCutterのバージョン1.1.3をリリースしました。

ハイライト

今回のリリースではデータ駆動テストのサポートを強化しています。サポートしている型が増えたので、これまで以上にテストデータを作りやすくなっています。

例えば、このようにテストデータを作ることができます。

gcut_add_datum("normal case",
               "expected", G_TYPE_CHAR, 'e',
               "input", G_TYPE_STRING, "Cutter",
               "n", G_TYPE_UINT, 4,
               NULL);

詳しくはリファレンスマニュアルの便利なテストデータ用API - gcut_add_datum()あたりを見てください。

Cutterユーザ(徐々に)増加中

Cutterが今のようなGLibベースの作りになってから2年半くらい経ちますが、徐々にユーザが増えてきました。最近、nfc-toolsという近距離無線通信(NFC)用のツールを開発しているオープンソースプロジェクトでも使われているのを見つけました。

groongaもCutterを採用しているプロジェクトで、Cutterのカバレッジ支援機能なども使っています。groongaプロジェクトではCutterが提供している支援機能だけではなく、コミットした人毎のカバレッジ率などもグラフ化して公開しています。今後のリリースで、Cutter本体がこのような見せ方を支援する機能を提供したいですね。

まとめ

Cutter 1.1.3の目玉機能とCutter採用プロジェクトについて紹介しました。

C・C++のプロジェクトで使うテスティングフレームワークを探しているならCutterも検討してみてはいかがでしょうか。

Tags: Cutter | このエントリの Delicious history 1 user | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-04-14

Muninプラグインの作り方

モニタリングツールMuninのプラグインの作り方を簡単に紹介します。ざっくり紹介しているので、説明を省いているところもあります。というのも、次の話の準備のための説明だからです。

Muninはネットワークのモニタリングに用いられることが多く、監視対象の各ホストに情報収集デーモン(munin-node)を配置し、そのデーモンがネットワークの情報などを収集します。監視ホストは定期的に情報収集デーモンにアクセスし、監視対象のホストの情報をグラフにします。グラフはHTMLで出力されます。PHPなどを導入しなくてもよいため、導入が楽ですし、リソース消費も少ないです。

監視対象ホスト上でどの情報を収集するかはインストール後に追加・削除できます。情報を収集する部分がプラグイン化されているためです。情報収集デーモンmunin-nodeにプラグインをインストールすればそのプラグインが収集する情報がモニタリング対象となり、自動的に監視ホスト上でグラフ化されます。

Muninはネットワークをモニタリングするために使われることが多いですが、プラグインが収集する情報はネットワーク情報に限定されていないため、仕様に従ったプラグインさえインストールすれば任意の情報をグラフ化することができます。

先日リリースされたmilter manager 1.5.0でもSMTPクライアントの同時接続数や各milterの適用結果をモニタリングするMuninプラグインを提供しています。SMTPクライアントの同時接続数はネットワークっぽい感じがしますが、milterの適用結果はあまりネットワークっぽい感じがしませんね。

それでは、まず、プラグインの仕様です。

Muninプラグインの仕様

Muninプラグインは以下の入力と出力を満たす実行ファイルです。RubyスクリプトでもシェルスクリプトでもC言語で書いてコンパイルしたプログラムでもかまいません。ただ、後述するmunin-node-configureを使いやすいのでスクリプト言語の方がオススメです。

入力
実行ファイルへの引数。以下のいずれかが渡される。
  • config: 収集する情報のメタ情報を要求
  • autoconf: ホスト上でプラグインが利用可能かどうかの判断を要求
  • snmpconf: 収集する情報のSNMP情報を要求
  • suggest: プラグインのパラメータを要求(後述)
  • 引数なし: 収集する値を要求
出力
出力は入力毎に異なります。
  • config: 以下のような1行1パラメータというフォーマットでグラフや収集する情報を出力します。パラメータのキーの一覧はprotocol-config - Munin - Trac(英語)にあります。

    echo "graph_title milter manager statistics"
    echo "graph_category milter-manager"
    # ...
    exit 0
    
  • autoconf: プラグインが利用可能なら"yes"を出力し、終了コード0で終了します。利用不可なら"no"を出力し、終了コード1で終了します。以下はシェルスクリプトでの例です。

    if [ "$available" = "yes" ]; then
      echo "yes"
      exit 0
    else
      echo "no (daemon isn't running)"
      exit 1
    fi
    
  • snmpconf: ConcisePlugins - Munin - Trac(英語)あたりを参照してください。
  • suggest: 利用可能なプラグインのパラメータを1行に1つずつ出力します。(詳しくは後述)

    echo "report"
    echo "status"
    exit 0
    
  • 引数なし: 以下のように収集したデータを出力します。

    echo "accept.value 4"
    echo "reject.value 31"
    exit 0
    

と、このくらいのことは検索すると日本語の情報もいろいろでてきます。が、なぜかautoconfとsuggestのことはあまりでてきません。これがMuninプラグインの便利なところな気がしますが、どうしてでしょう。

ということで、そのあたりの紹介もします。

munin-node-configure

Muninにはmunin-node-configureというプラグインの追加・削除を自動的に行ってくれるツールがついています。ブログなどに書かれているプラグインの追加方法は以下のように手動でやっていることが多いです。

% sudo ln -s /usr/share/munin/plugins/cpu /etc/munin/plugins/

しかし、手動で追加・削除は面倒です。システム管理をしている人ならなるべく自動化したいと思うことでしょう。

munin-node-configureを使うと、プラグインの引数に"autoconf"を渡して、プラグイン自身に利用可能かを判断させ、その結果に応じて追加・削除することができます。自作プラグインでも"autoconf"に対応することで管理がだいぶ楽になります。ブログなどに載っている自作Muninプラグインは一回限りの利用だけを想定しているからなのか、"autoconf"に対応しているものが少ないように見えます。

"autoconf"に対応していることをmunin-node-configureに伝えるためには以下の行をプラグイン内に埋め込んでおく必要があります。バイナリの実行ファイルよりスクリプトの方がよいと書いたのはこのためです。

#%# family=auto
#%# capabilities=autoconf

このような行があるとmunin-node-configureは"autoconf"引数付きで呼び出してくれます。このときに"yes"と出力すれば自動で追加対象にしてくれます。ConcisePlugins - Munin - Trac(英語)あたりも参照してください。

munin-node-configureを利用した自動追加・削除は以下のコマンドでできます。

% sudo -H /usr/sbin/munin-node-configure --shell --remove-also | sudo -H sh

munin-nodeを再起動すれば反映されます。

% sudo /etc/init.d/munin-node restart

最近だと、/etc/init.d/以下のスクリプトを直接実行するよりもservice(8)を使う方がよいでしょう。

ワイルドカードプラグイン

ワイルドカードプラグインとは1つのプラグインで複数のグラフの情報を収集できるプラグインのことです。(1.4からは同じようなことができるマルチグラフプラグインという仕組みも増えています。)

ワイルドカードプラグインはプラグインファイルのファイル名の最後が「_」で終わっています。使うときは、ファイル名の最後にパラメータをつけたシンボリックリンクを張ります。

例えば、「milter_manager_」というワイルドカードプラグインに「status」というパラメータを渡したいときは以下のようにします。

% sudo ln -s /usr/share/munin/plugins/milter_manager_ /etc/munin/plugins/milter_manager_status

プラグイン内では自分のファイル名(シェルやRubyでいえば$0)を見てパラメータを受けとります。これにより、1つのプラグインファイルで複数の情報を収集することができるようになります。

そして、どのパラメータを指定すればよいかをプラグインに判定させて自動でワイルドカードプラグインを追加・削除することもできます。このときに使われる入力が"suggest"です。この機能に対応していることをmunin-node-configureに教えるためにはプラグイン内に以下のような行を書いておきます。

#%# family=auto
#%# capabilities=autoconf suggest

これを書いておけばmunin-node-configureが"suggest"引数付きでワイルドカードプラグインを実行してくれます。このとき、"status"と"report"というパラメータが利用できるのであれば、以下のように1行につき1パラメータで出力します。

status
report

こうすると、1つのワイルドカードプラグインを2種類のプラグインとしてインストールしてくれます。このとき、インストールされるプラグインは「milter_manager_status」、「milter_manager_report」というような名前になります。

追加・削除方法は変わりません。

% sudo -H /usr/sbin/munin-node-configure --shell --remove-also | sudo -H sh

munin-nodeの再起動も忘れないようにしましょう。

% sudo /etc/init.d/munin-node restart

まとめ

Muninプラグインの作り方を(実例は示さずに)紹介しました。実例を示さない代わりに本家のドキュメントの概要箇所をリンクしています。具体例はドキュメントやインストール済みのプラグインを見てください。プラグインは小さいシェルスクリプトかPerlスクリプトのことが多いので、読むとすぐにわかるでしょう。

また、管理がとても楽になり便利なのに、なぜかあまり触れられていない"autoconf"と"suggest"についても説明しました。自分でMuninプラグインを作るときは"autoconf"に対応することをオススメします。もちろん、ワイルドカードプラグインにする場合は"suggest"にも対応することをオススメします。

いつか、ソフトウェア開発にもMuninを使う例を紹介したいものです。例えば、自動化された単体テストのテスト数やカバレッジ率などもMuninを使ってグラフ化することができます。

つづき: 2010-05-07
2010-04-08

採用活動を再開

昨年の夏くらいから採用活動を中止していましたが、再開することにしました。

応募条件は変わらず「プログラミングが好きなこと」です。フリーソフトウェアに理解があることが望ましいです。

1名のみの募集ですが、興味のある方は採用情報を参照してください。

このエントリの Delicious history | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-04-01

Ruby 1.9.xでRange#include?を高速に動かす方法

Ruby 1.9.xではRange#include?の実装が変わり、Ruby 1.8.xよりも圧倒的に遅くなるケースがあります。これは、Ruby 1.9.xへ移行したときの有名なハマりポイントの1つでしょう。

例えば、こんなケースです。

require 'time'
require 'benchmark'

Benchmark.bm(10) do |bm|
  march = Time.parse("2010/03/01")...Time.parse("2010/04/01")
  march_15 = Time.parse("2010/03/15")
  bm.report("include?") do
    march.include?(march_15)
  end
end

2010年3月15日が2010年3月に入っているかを調べています。

これをRuby 1.8.7で動かすとこうなります。

% ruby -v /tmp/range-include.rb
ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux]
                user     system      total        real
include?    0.000000   0.000000   0.000000 (  0.000011)

一瞬ですね。

Ruby 1.9.1で動かすとこうなります。

% ruby1.9.1 -v /tmp/range-include.rb
ruby 1.9.1p378 (2010-01-10 revision 26273) [x86_64-linux]
                user     system      total        real
include?    1.360000   0.030000   1.390000 (  1.766556)

1万倍以上も遅くなります。

原因

どうしてこうなるのかというと、1.8.xのRange#include?と1.9.xのRange#include?では引数の値の見つけ方が異なるからです。

1.8.xでは引数が範囲の最初の値より大きいかつ最後の値より小さい、ことだけを確認していました。1.9.xでは範囲の最初から最後まで順に繰り返し、その中に引数と==な値が含まれるかどうかを確認します。コードで表すとこんな感じです。

def range_include_18(range, value)
  range.begin < value and value < range.end
end

def range_include_19(range, value)
  range_value = range.begin
  loop do
    return true if range_value == value
    range_value = range_value.succ
    break if range_value >= range.end
  end
  false
end

Time#succは1秒先の時間を返します。そのため、一回のRange#include?で最大2678400回ループをまわすことになります(範囲に入っていないとき、つまり、falseを返すとき)。

(Time.parse("2010/03/01")...Time.parse("2010/04/01")).to_a.size # => 2678400

真ん中あたりのTime.parse("2010/03/15")でも1209601番目なので、1.8.xのRange#include?と比べてだいぶ遅くなるというわけです。

(Time.parse("2010/03/01")..Time.parse("2010/03/15")).to_a.size # => 1209601

解決法

1.9.xには1.8.xのRange#include?と同じように比較するメソッドRange#cover?が追加されているのでそれを使うとよいでしょう。

require 'time'
require 'benchmark'

Benchmark.bm(10) do |bm|
  march = Time.parse("2010/03/01")...Time.parse("2010/04/01")
  march_15 = Time.parse("2010/04/01")
  bm.report("cover?") do
    march.cover?(march_15)
  end
end

実行するとたしかに速いです。

% ruby1.9.1 -v /tmp/range-cover.rb
ruby 1.9.1p378 (2010-01-10 revision 26273) [x86_64-linux]
                user     system      total        real
cover?      0.000000   0.000000   0.000000 (  0.000010)

ただし、1.8.xにはRange#cover?はないので、Range#cover?を使うと1.9.x専用のスクリプトになってしまいます。

高速なRange#include?

実は、1.9.xのRange#include?も、数字の範囲または文字の範囲、の場合は高速に動作します。この場合だけ特別扱いされていて1.8.xと同様の比較方法を用いているからです。つまり、どうにかして数字の範囲か文字の範囲に落としこめば1.8.xでも1.9.xでも高速に動作するようなスクリプトを書けるということです。

Timeの場合はTime#to_iで数値にしてしまうのがよいでしょう。

require 'time'
require 'benchmark'

Benchmark.bm(10) do |bm|
  march = Time.parse("2010/03/01")...Time.parse("2010/04/01")
  march_15 = Time.parse("2010/03/15")
  bm.report("Time") do
    march.include?(march_15)
  end

  march_integer = (Time.parse("2010/03/01").to_i)...(Time.parse("2010/04/01").to_i)
  march_15_integer = Time.parse("2010/03/15").to_i
  bm.report("Integer") do
    march_integer.include?(march_15_integer)
  end
end

結果は一目瞭然です。

% ruby1.9.1 -v /tmp/range-include-integer.rb
ruby 1.9.1p378 (2010-01-10 revision 26273) [x86_64-linux]
                user     system      total        real
Time        1.230000   0.030000   1.260000 (  1.504888)
Integer     0.000000   0.000000   0.000000 (  0.000009)

まとめ

徐々にRuby 1.9.xを使う人が増えているかもしれない、ということで、Ruby 1.9.xを使った場合にハマりそうなポイントとその解決法を紹介しました。1.9.xでは変わったことがたくさんあります。1.9.xのことをもっと知って上手に付き合ってみてはいかがでしょうか。

Tags: Ruby | このエントリの Delicious history 9 users | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-03-25

るびま0029号

るびま0029号がリリースされていますね。おめでとうございます。

せっかくなので少し紹介します。

ActiveLdapを使ってみよう

今回のるびまにはRubyでLDAPを操作するための便利ライブラリActiveLdapの記事の後編ActiveLdap を使ってみよう(後編)が入っています。

後編では今まではあまり文書化されていなかったフィルタのこと関連性のことにも触れています。全てのエントリを扱うクラスは、知る人ぞ知るのノウハウではないでしょうか。(ActiveLdap付属のサンプルアプリケーションでは使われていますが文書化はされていなかったはず。)LDAPサーバの設定やデータを確認する場合にはLDAP のスキーマ情報を ActiveLdap から参照するあたりの情報が役に立ちます。ldapsearchなどではなく、irbでLDAPサーバの情報を確認できるので、環境構築時やデバッグ時にとても便利です。(irbの補完機能を有効にするとより便利です。)

記事を書いている高瀬さんActiveLdapのチュートリアルの翻訳もしている頼もしい方です。ActiveLdapに興味はあるけどまだよく知らないという方は、まず、 ActiveLdap を使ってみよう(前編)を読んでからチュートリアルを読んで、最後に後編を読むのがよいのではないでしょうか。

0029-RubyNewsにもActiveLdap 1.2.1のリリースが載っていますね。

とちぎRuby会議02

発表者として参加したとちぎRuby会議02のレポート記事RegionalRubyKaigi レポート (10) とちぎ Ruby 会議 02もあります。レポートにもありますが、とちぎRuby会議02のお題が他のRegionalRubyKaigiと違ったものだったので、独特な内容になっていましたね。

Rubyベストプラクティス

0029 号 巻頭言で紹介されているRuby 1.9向けに書かれたRubyベストプラクティスという本がそろそろ発売するようです。テストまわりの章だけレビューに参加しました。

少しくせがある印象なので、Ruby初心者の方にはつらいかもしれません。自分で判断できる程度にRubyを知っている方ならいろいろ考えながら読むとおもしろいかもしれません。

まとめ

るびまがリリースされていたので紹介しました。

ところで、みなさんは編集後記は読んでいるのでしょうか。たまにおもしろかったりするので、読んでいない方は読んでみてはいかがでしょうか。短いのでさっと目を通せます。

Tags: Ruby | このエントリの Delicious history 1 user | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-03-16

test-unit 2.0.7とCutter 1.1.1をリリース

先日、Ruby用のxUnit系テスティングフレームワークtest-unit 2.0.7とC・C++用のxUnit系テスティングフレームワークCutter 1.1.1がリリースされました。

どちらも、テストの書きやすさ(テストをキレイに書けるとテストを保守しやすい)だけではなく、テストが失敗した時に「できるだけ素早く問題の原因にたどり着ける」ことも重視しています。

Rails/Rack界隈ではCucumberWebratcapybaraなどを使って、「"ログイン"ボタンをクリックする」とか「click_link("ログイン")」などと、直感的にテストを書けるようになっています。では、テストが失敗したときの結果はどのように表示されるでしょうか。HTML全体やテキスト全体が表示されて、「"ログイン"というボタン(リンク)はなかったよ」と言われたらどうでしょう。あなたのコードはどこが悪かったのでしょう。

そういうときに、失敗結果を見て、すぐに「あぁ、ここが悪いかも!」と作業を進めていけるようなテスティングフレームワークにしたいものです。開発はデバッグの連続なのですから、よりスムーズにデバッグ作業を進める手助けとなるツールを使って開発したいですよね。

test-unitやCutterはWebアプリケーション用に特別なサポート機能は提供していないので上記のようなことをうまい具合に解決できるわけではないのですが、ライブラリのテストでは上記のようなことをうまい具合に解決する機能を提供しています。

Cutterのインストール方法がより簡単に

一応、リリースで変わったことを少し書いておきます。

機能面でもよくなっているのですが、インストールまわりだけにしておきます。

Cutterプロジェクトでは、これまでDebian, Ubuntu用のapt-lineとMacPortsのPortfileを提供していましたが、今回のリリースから、FedoraのYumリポジトリも提供するようにしました。以下でインストールできます。yumの管理下に入るのでアップデートも簡単ですね。

% sudo rpm -Uvh http://cutter.sourceforge.net/fedora/cutter-repository-1.0.0-0.noarch.rpm
% sudo yum install cutter

test-unitの方は変わらずgemをサポートしているので、こちらもインストール・アップデートが簡単ですね。

書きやすさだけではなくデバッグしやすさも重視したテスティングフレームワークに興味のある方は使ってみてはいかがでしょうか。

2010-03-11

Debian GNU/Linux上でRPMパッケージ・Yumリポジトリを作って公開する方法: milter managerを例にして

注: FedoraやCentOSのRPMパッケージャーが書いた文章ではありません。FedoraやCentOSのRPMパッケージメンテナになりたい方はFedoraやCentOSが公式に配布している文書の方をお勧めします。例えば、How to create an RPM package - FedoraProjectという文書があります。

Debianパッケージの作り方と公開方法: groongaを例にしてのRPM版のような内容です。

ここでは、迷惑メール対策ソフトウェアmilter managerを例にしてDebian GNU/Linux上でCentOS 5.4向けのRPMパッケージを作成し、それを提供するYumリポジトリを作成・公開する方法を紹介します。RPMパッケージの作成では、1つのspecファイルから複数のパッケージを作成します。この方法は、ライブラリを提供するソフトウェアの場合に多く用いられます。

また、Yumリポジトリを登録するRPMも作成します。そのため、ユーザには以下のようにYumリポジトリを登録してもらうことになります。

Yumリポジトリを登録:

% sudo rpm -Uvh http://milter-manager.sourceforge.net/centos/5/milter-manager-repository-1.0.0-0.noarch.rpm

インストール:

% sudo yum install -y milter-manager

ここで紹介する方法を自動化するスクリプト群はmilter managerのリポジトリで公開されています。ここで紹介する方法でRPMパッケージ・Yumリポジトリを作成・公開する場合は参考にしてください。

それでは、まずはパッケージの作り方です。

パッケージの作り方

RPMパッケージを作るためには.specを作ります。ここでは、複数のパッケージを生成する.specを作成するので、まず、どれをどのパッケージにいれるかを検討します。

構成

milter managerはmilterプロトコルを実装したライブラリ、それを利用したmilter管理アプリケーション、ユーティリティツールで構成されています。この場合、以下のように複数のパッケージに分解することが多いようです。

  • libXXX: ライブラリを使用しているソフトウェアを実行するために必要なファイルを提供するパッケージ。/usr/lib/libXXX.soなどを提供する。
  • libXXX-devel: ライブラリを使用しているソフトウェアをビルドするために必要なファイルを提供するパッケージ。/usr/include/XXX/*.hなどを提供することが多い。libXXXに依存する。
  • XXX: (ライブラリではなく)コマンドを提供するパッケージ。libXXXに依存する。

milter managerの場合は以下のパッケージを作成することとします。

  • libmilter-toolkit: /usr/lib/libmilter-client.soなどを提供する。
  • libmilter-toolkit-devel: /usr/include/milter-manager/milter/client.hなどを提供。
  • libmilter-compatible: libmilterとABI互換の/usr/lib/libmilter.soを提供する。
  • libmilter-compatible-devel: libmilterとAPI互換の/usr/include/milter-manager/libmilter/*.hを提供する。
  • milter-manager: milter-managerコマンドを提供する。
  • milter-manager-munin-plugin: milter managerの統計情報を収集するmunin-nodeのプラグインを提供する。

雛形

複数のパッケージを作成する.specは以下のような内容になります。簡単ですね、とは言えないくらいの長さです*1

Summary: 簡単な説明
Name: (メイン)パッケージ名(「milter-manager」など)
Version: バージョン番号(「1.5.0」など)
Release: リリースバージョン(最初は「0{?dist}」にしておけばよい)
License: ライセンス(「GPLv3+」など)
URL: ソフトウェアのサイトのURL
     (「http://milter-manager.sourceforge.net/」など)
Group: (メイン)パッケージが属するグループ
       (「System Environment/Daemons」など。
         「/usr/share/doc/rpm/GROUPS」に一覧がある。)
Source: ソースのアーカイブのURL
        (「http://downloads.sourceforge.net/milter-manager/milter-manager-1.5.0.tar.gz」など)
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n)
           (常に↑でOK)
BuildRequires: ビルドに必要なパッケージ
BuildRequires: 必要な分だけ書く
Requires: 動作に必要なパッケージ
Requires: 必要な分だけ書く

%description
詳細なパッケージの説明。複数行になってもOK。

% package -n サブパッケージ名
          (「-n」をつけないと「『パッケージ名』-『サブパッケージ名』」
            というパッケージ名になるので、「libXXX」というパッケージ名を
            つけるときは「-n」をつけないといけない)
Summary: サブパッケージの簡単な説明
Group: サブパッケージの属するグループ

% description -n サブパッケージ名
              (「-n」の意味は「%package」と同じ)
詳細なサブパッケージの説明。複数行になってもOK。

%prep
%setup -q
%build
%configure オプション
          (「--with-default-effective-user=milter-manager」など)
make %{?_smp_mflags}

% install
rm -rf %{buildroot}
make install DESTDIR=%{buildroot}

%clean
rm -rf %{buildroot}

%files
%defattr(-, root, root, -)
(メイン)パッケージに含めるファイルを指定。

%files -n サブパッケージ名
%defattr(-, root, root, -)
サブパッケージに含めるファイルを指定。

%changelog
* 日付 名前 <メールアドレス>
- (バージョン番号-リリース番号)
- new upstream release

1つの.specで1つのパッケージを作る場合は%package -n サブパッケージ名%description -n サブパッケージ名%files -n サブパッケージ名がなくなるだけで、基本的な記述は変わりません。

milter managerの場合は以下のような.specになっています。%prepostなどが増えていますが、これらは、それぞれインストール前・インストール後に実行するシェルスクリプトを指定しているだけです。

そこそこ長いので、詳細に興味がない場合は読み飛ばしてください。

Summary: A milter to use milters effectively
Name: milter-manager
Version: 1.5.0
Release: 11%{?dist}
License: GPLv3+, LGPL3+, AGPL3+, GFDL, Public Domain
URL: http://milter-manager.sourceforge.net/
Group: System Environment/Daemons
Source: http://downloads.sourceforge.net/milter-manager/milter-manager-1.5.0.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n)
BuildRequires: intltool
BuildRequires: gettext
BuildRequires: gcc
BuildRequires: make
BuildRequires: glib2-devel
BuildRequires: ruby
BuildRequires: ruby-devel
Requires: glib2
Requires: ruby
Requires: libmilter-toolkit = %{version}-%{release}
Requires(pre): /usr/bin/getent, /usr/sbin/useradd
Requires(pre): /usr/bin/id, /usr/sbin/groupadd
Requires(post): /sbin/chkconfig
Requires(preun): /sbin/service, /sbin/chkconfig
Requires(postun): /sbin/service, /sbin/chkconfig, /usr/sbin/userdel

%description
milter manager administrates milters instead of MTA to reduce milter
administration cost and combine milters flexibly.

%package -n libmilter-toolkit
Summary: A milter protocol library
Group: System Environment/Libraries

%description -n libmilter-toolkit
Both of client-side and server-side milter protocol are implemented.
This package contains the library files required for running services
built using libmilter-toolkit.

%package -n libmilter-toolkit-devel
Summary: Development files for libmilter-toolkit
Group: Development/Libraries
Requires: libmilter-toolkit = %{version}-%{release}

%description -n libmilter-toolkit-devel
This package contains the headers, and other support files
required for developing applications against libmilter-toolkit.

%package -n libmilter-compatible
Summary: libmilter API and ABI compatible milter library
Group: System Environment/Libraries
Requires: libmilter-toolkit = %{version}-%{release}

%description -n libmilter-compatible
A libmilter API and ABI compatible library based on libmilter-toolkit.
This package contains the library files required for running services
built using Sendmail libmilter or libmilter-compatible.

%package -n libmilter-compatible-devel
Summary: Development files for libmilter-compatible
Group: Development/Libraries
Requires: libmilter-compatible = %{version}-%{release}
Requires: libmilter-toolkit-devel = %{version}-%{release}

%description -n libmilter-compatible-devel
This package contains the headers, and other support files
required for developing applications against libmilter-compatible.

%package -n milter-manager-munin-plugin
Summary: Munin plugin for milter manager
Group: System Environment/Libraries
Requires: milter-manager = %{version}-%{release}
Requires: munin-node

%description -n milter-manager-munin-plugin
This package contains the munin plugin for munin-node.

%prep
%setup -q

%build
%configure								\
    --with-default-effective-user=milter-manager			\
    --with-default-effective-group=milter-manager			\
    --with-default-socket-group=smmsp					\
    --with-default-pid-file=/var/run/milter-manager/milter-manager.pid	\
    --with-default-connection-spec=unix:/var/run/milter-manager/milter-manager.sock
make %{?_smp_mflags}

%install
rm -rf %{buildroot}
make install DESTDIR=%{buildroot}

mkdir -p %{buildroot}%{_initrddir}
install -m 755 data/init.d/redhat/milter-manager %{buildroot}%{_initrddir}/milter-manager

mkdir -p %{buildroot}%{_sysconfdir}/sysconfig
install -m 644 data/init.d/redhat/sysconfig/milter-manager %{buildroot}%{_sysconfdir}/sysconfig/milter-manager

mkdir -p %{buildroot}%{_sysconfdir}/cron.d
install -m 600 data/cron.d/redhat/milter-manager-log %{buildroot}%{_sysconfdir}/cron.d/milter-manager-log

mkdir -p %{buildroot}%{_localstatedir}/run/milter-manager/

mkdir -p %{buildroot}%{_sysconfdir}/httpd/conf.d/
cat <<EOC > %{buildroot}%{_sysconfdir}/httpd/conf.d/milter-manager-log.conf
Alias /milter-manager-log/ /var/lib/milter-manager/public_html/log/
EOC

mv %{buildroot}%{_datadir}/milter-manager/munin/ %{buildroot}%{_datadir}/
mkdir -p %{buildroot}%{_sysconfdir}/munin/plugin-conf.d/
cat <<EOC > %{buildroot}%{_sysconfdir}/munin/plugin-conf.d/milter-manager
[milter_manager_*]
  user milter-manager
  env.logdir /var/lib/milter-manager/public_html/log
EOC

%clean
rm -rf %{buildroot}

%pre
if ! /usr/bin/getent group milter-manager &>/dev/null; then
    /usr/sbin/groupadd -r milter-manager || \
        %logmsg "Unexpected error adding group \"milter-manager\". Aborting installation."
fi
if ! /usr/bin/id milter-manager &>/dev/null; then
    /usr/sbin/useradd -r -s /sbin/nologin -c 'milter manager' \
        -d %{_localstatedir}/lib/milter-manager --create-home \
        -g milter-manager milter-manager || \
        %logmsg "Unexpected error adding user \"milter-manager\". Aborting installation."
fi

%post
/sbin/chkconfig --add milter-manager
/bin/mkdir -p /var/run/milter-manager
/bin/chown -R milter-manager:milter-manager /var/run/milter-manager

%post -n milter-manager-munin-plugin
/usr/sbin/munin-node-configure --shell | \
    grep -e '\(milter_manager_\|\(postfix\|sendmail\)_processes\)' | \
    sh
[ -f /var/lock/subsys/munin-node ] && \
    /sbin/service munin-node restart > /dev/null 2>&1
:

%preun
if [ $1 -eq 0 ] ; then
    /sbin/service milter-manager stop > /dev/null 2>&1
    /sbin/chkconfig --del milter-manager
fi

%postun
if [ $1 -ge 1 ] ; then
    /sbin/service milter-manager condrestart > /dev/null 2>&1
fi

if [ $1 -eq 0 ]; then
    /usr/sbin/userdel -r milter-manager || \
        %logmsg "User \"milter-manager\" could not be deleted."
fi

%postun -n milter-manager-munin-plugin
if [ $1 -eq 0 ]; then
    rm %{_sysconfdir}/munin/plugins/milter_manager_* > /dev/null 2>&1
    rm %{_sysconfdir}/munin/plugins/postfix_processes > /dev/null 2>&1
    rm %{_sysconfdir}/munin/plugins/sendmail_processes > /dev/null 2>&1
    [ -f /var/lock/subsys/munin-node ] && \
        /sbin/service munin-node restart > /dev/null 2>&1
    :
fi

%files
%defattr(-, root, root, -)
%doc ChangeLog ChangeLog.toolkit README README.ja NEWS NEWS.ja TODO
%doc %{_datadir}/milter-manager/license/
%doc %{_datadir}/gtk-doc/html/milter-manager/
%{_bindir}/milter-manager-log-analyzer
%{_sbindir}/milter-manager
%{_includedir}/milter-manager/milter/manager.h
%{_includedir}/milter-manager/milter/manager/
%{_libdir}/libmilter-manager.*
%{_libdir}/milter-manager/binding/
%{_libdir}/milter-manager/module/
%{_libdir}/pkgconfig/milter-manager.pc
%{_mandir}/man1/milter-manager.*
%{_mandir}/man1/milter-manager-log-analyzer.*
%{_mandir}/ja/man1/milter-manager.*
%{_mandir}/ja/man1/milter-manager-log-analyzer.*
%{_initrddir}/milter-manager
%{_datadir}/milter-manager/admin/
%{_sysconfdir}/milter-manager/cron.d/
%{_sysconfdir}/milter-manager/init.d/
%{_sysconfdir}/milter-manager/rc.d/
%{_sysconfdir}/cron.d/
%config %{_sysconfdir}/sysconfig/milter-manager
%config %{_sysconfdir}/milter-manager/milter-manager.conf
%config %{_sysconfdir}/milter-manager/defaults/
%config %{_sysconfdir}/milter-manager/applicable-conditions/
%config %{_sysconfdir}/httpd/conf.d/milter-manager-log.conf

%defattr(-, milter-manager, milter-manager, 0755)
%dir %{_localstatedir}/run/milter-manager/

%files -n libmilter-toolkit
%defattr(-,root,root)
%doc ChangeLog ChangeLog.toolkit README README.ja NEWS NEWS.ja TODO
%doc %{_datadir}/milter-manager/license/
%{_bindir}/milter-test-client
%{_bindir}/milter-test-server
%{_bindir}/milter-performance-check
%{_libdir}/libmilter-core.so.*
%{_libdir}/libmilter-client.so.*
%{_libdir}/libmilter-server.so.*
%{_mandir}/man1/milter-test-client.*
%{_mandir}/man1/milter-test-server.*
%{_mandir}/man1/milter-performance-check.*
%{_mandir}/ja/man1/milter-test-client.*
%{_mandir}/ja/man1/milter-test-server.*
%{_mandir}/ja/man1/milter-performance-check.*

%files -n libmilter-toolkit-devel
%defattr(-,root,root)
%doc ChangeLog ChangeLog.toolkit README README.ja NEWS NEWS.ja TODO
%doc %{_datadir}/milter-manager/license/
%doc %{_datadir}/gtk-doc/html/milter-manager/
%{_includedir}/milter-manager/milter/core.h
%{_includedir}/milter-manager/milter/core/
%{_includedir}/milter-manager/milter/client.h
%{_includedir}/milter-manager/milter/client/
%{_includedir}/milter-manager/milter/server.h
%{_includedir}/milter-manager/milter/server/
%{_libdir}/libmilter-core.so
%{_libdir}/libmilter-core.la
%{_libdir}/libmilter-client.so
%{_libdir}/libmilter-client.la
%{_libdir}/libmilter-server.so
%{_libdir}/libmilter-server.la
%{_libdir}/pkgconfig/milter-core.pc
%{_libdir}/pkgconfig/milter-client.pc
%{_libdir}/pkgconfig/milter-server.pc

%files -n libmilter-compatible
%defattr(-,root,root)
%doc ChangeLog ChangeLog.toolkit README README.ja NEWS NEWS.ja TODO
%doc %{_datadir}/milter-manager/license/
%{_libdir}/milter-manager/libmilter.so.*

%files -n libmilter-compatible-devel
%defattr(-,root,root)
%doc ChangeLog ChangeLog.toolkit README README.ja NEWS NEWS.ja TODO
%doc %{_datadir}/milter-manager/license/
%doc %{_datadir}/gtk-doc/html/milter-manager/
%{_includedir}/milter-manager/libmilter/
%{_libdir}/milter-manager/libmilter.so
%{_libdir}/milter-manager/libmilter.la
%{_libdir}/pkgconfig/libmilter.pc

%files -n milter-manager-munin-plugin
%defattr(-,root,root)
%doc ChangeLog ChangeLog.toolkit README README.ja NEWS NEWS.ja TODO
%doc %{_datadir}/milter-manager/license/
%{_datadir}/munin/
%config %{_sysconfdir}/munin/plugin-conf.d/

%changelog
* Thu Feb 17 2010 Kouhei Sutou <kou@clear-code.com>
- (1.5.0-11)
- new upstream release

* Thu Oct 29 2009 Kouhei Sutou <kou@clear-code.com>
- (1.4.1-0)
- new upstream release

* Thu Oct 13 2009 Kouhei Sutou <kou@clear-code.com>
- (1.4.0-0)
- new upstream release

* Wed Sep 16 2009 Kouhei Sutou <kou@clear-code.com>
- (1.3.1-0)
- new upstream release

* Wed Aug 12 2009 Kouhei Sutou <kou@clear-code.com>
- (1.3.0-0)
- new upstream release

* Fri Jul 17 2009 Kouhei Sutou <kou@clear-code.com>
- (1.2.0-0)
- new upstream release

* Fri Jul 03 2009 Kouhei Sutou <kou@clear-code.com>
- (1.1.1-0)
- new upstream release

* Tue Jun 02 2009 Kouhei Sutou <kou@clear-code.com>
- (1.1.0-0)
- initial 1.1.x development seriese release

* Thu Apr 16 2009 Kouhei Sutou <kou@clear-code.com>
- (1.0.0-1)
- initial stable release

ビルド: rinse

Debian GNU/Linux上でCentOS用のRPMパッケージをビルドすることは茨の道です。そこで、CentOS用のchroot環境を作って、そこでビルドすることにします。こうすることで、CentOSの実機がなくてもビルドできる上に、きれいな環境でビルドすることもできます。

Debian GNU/LinuxやUbuntuのchroot環境を作るにはdebootstrapが便利です。CentOSやFedoraのchroot環境を作るにはrinseが便利です。

/var/lib/chroot/centos-i386/以下にCentOS 5.4 i386用のchroot環境を構築するには以下のようにします。

% sudo aptitude install -y rinse
% sudo mkdir -p /var/lib/chroot/centos-i386/etc/rpm/
% sudo sh -c "echo i386-centos-linux > /var/lib/chroot/centos-i386/etc/rpm/platform"
% sudo rinse --arch i386 --distribution centos-5 --directory /var/lib/chroot/centos-i386/

/etc/fstabに以下を追記:

/dev          /var/lib/chroot/centos-i386/dev     none   bind     0 0
devpts-chroot /var/lib/chroot/centos-i386/dev/pts devpts defaults 0 0
proc-chroot   /var/lib/chroot/centos-i386/proc    proc   defaults 0 0

本質的な部分はsudo rinse ...だけです。sudo rinse ...の前にあるRPMパッケージで利用するプラットフォームを指定している箇所はrinseのバグを回避するためです*2。これがないとamd64のDebian GNU/Linux環境でi386のCentOS環境を作成することができません。

/etc/fstabへの追記は、再起動する度にmountしなおすのが面倒だからです。

この作業はbuild-in-chroot.shの中で自動化されています。

ビルド: chroot

CentOSのchroot環境ができたら、その環境にビルド専用アカウントを作成し、そのユーザでビルドします。ここで紹介する作業はbuild-rpm.shの中で自動化されています。ここでは、その中の一部を説明します。

まず、ビルド用ユーザが存在しない場合はユーザを作成します。

USER_NAME=milter-manager-build
if ! id $USER_NAME >/dev/null 2>&1; then
  useradd -m $USER_NAME
fi

次に、そのビルド用ユーザが実行するビルドスクリプトを作成します。.tar.gzや.specなどのビルドに必要なファイルはchroot環境の/tmp/以下(/var/lib/chroot/centos-i386/tmp/以下)に事前にコピーしておきます。それぞれの処理内容はコメントとして説明しています。

BUILD_SCRIPT=/tmp/build-milter-manager.sh
VERSION=`cat /tmp/milter-manager-version`

cat <<EOF > $BUILD_SCRIPT
#!/bin/sh

# RPMパッケージのビルドは~/rpm/以下で行う。
if [ ! -f ~/.rpmmacros ]; then
    cat <<EOM > ~/.rpmmacros
%_topdir \$HOME/rpm
EOM
fi

# RPMパッケージ作成に必要なディレクトリを作成。
# rpmdevtoolsパッケージに含まれているrpmdev-setuptreeコマンド
# でも同様のことができるよう。
mkdir -p rpm/SOURCES
mkdir -p rpm/SPECS
mkdir -p rpm/BUILD
mkdir -p rpm/RPMS
mkdir -p rpm/SRPMS

# ソースと.specを配置。
cp /tmp/milter-manager-$VERSION.tar.gz rpm/SOURCES/
cp /tmp/milter-manager.spec rpm/SPECS/

# RPMパッケージ作成。
rpmbuild -ba rpm/SPECS/milter-manager.spec
EOF

このビルドスクリプトをビルド用ユーザで実行します。

chmod +x $BUILD_SCRIPT
su - $USER_NAME $BUILD_SCRIPT

ビルドが成功するとビルド用ユーザの~/rpm/RPMS/i386/以下にRPMパッケージができます*3

RPMパッケージができたら、それらに署名をします。

署名

RPMでは、パッケージに署名することによりパッケージ作成者のなりすましを防止することができます。署名の検証を無効にすることもできるので、署名なしのRPMパッケージでもYumリポジトリで公開・インストールすることはできますが、よほどの理由がない場合は署名をするべきでしょう。

パッケージへの署名はパッケージ作成時にも作成後にも行うことができます。パッケージ作成後に行う場合はrpmコマンドの--resignオプションを使います。署名する鍵は_gpg_nameで指定します。

% rpm -D "_gpg_name Kouhei Sutou <kou@clear-code.com>" --resign XXX.rpm

これをそれぞれの.rpmに対して行います。

RPMパッケージに署名したら、署名済みRPM使ってYumリポジトリを作ります。

Yumリポジトリの作成

milter managerは安定版と開発版の2つのリリースラインがあります。そのため、以下のようなディレクトリ構成でYumリポジトリも2つ作ります。下の図のdevelopmentとstableがそれぞれYumリポジトリになります。各YumリポジトリはSRPMS, i386, x86_64ディレクトリを持ち、その下にビルドしたRPMパッケージを配置します。

.
+--- centos/
     +--- 5/
          +--- development
          |    +--- SRPMS/
          |    |    +--- milter-manager-1.5.0-0.src.rpm
          |    |    +--- ...
          |    +--- i386/
          |    |    +--- CentOS/
          |    |         +--- milter-manager-1.5.0-0.i386.rpm
          |    |         +--- ...
          |    +--- x86_64/
          |         +--- CentOS/
          |              +--- milter-manager-1.5.0-0.x86_64.rpm
          |              +--- ...
          +--- stable
               +--- SRPMS/
               |    +--- milter-manager-1.4.1-0.src.rpm
               |    +--- ...
               +--- i386/
               |    +--- CentOS/
               |         +--- milter-manager-1.4.1-0.i386.rpm
               |         +--- ...
               +--- x86_64/
                    +--- CentOS/
                         +--- milter-manager-1.4.1-0.x86_64.rpm
                         +--- ...

このように.rpmを配置したらSRPMS, i386, x86_64のそれぞれのディレクトリに対してcreaterepoコマンドを実行します。この作業はupdate-repository.shで自動化されています。

for dir in centos/5/*/*; do
    createrepo $dir
done

createrepoコマンドを実行するとそれぞれのディレクトリの下にrepodataというディレクトリが作成され、パッケージの情報が格納されます。

これでYumリポジトリは完成です。HTTPでアクセスできるところにアップロードしてください。

http://milter-manager.sourceforge.net/centos/以下でアクセスできるところにアップロードしたとすると、Yumのリポジトリ指定は以下のようになります。

/etc/yum.repos.d/milter-manager.repo:

[milter-manager]
name=milter manager for CentOS-$releasever
baseurl=http://milter-manager.sourceforge.net/centos/$releasever/stable/$basearch/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-milter-manager

gpgkeyはRPMに署名した時に使ったキーの公開鍵が入ったファイルを指定します。公開鍵は以下のコマンドで出力できます。

% gpg --export --armor "Kouhei Sutou <kou@clear-code.com>"

これを/etc/pki/rpm-gpg/RPM-GPG-KEY-milter-managerに置くことになります。

つまり、Yumリポジトリを登録するためには以下の作業が必要になります。

  1. /etc/yum.repos.d/milter-manager.repoの作成
  2. 公開鍵の配置

2段階にわかれているので面倒ですね。Yumリポジトリ登録の手間を軽減するために、「Yumリポジトリを登録するRPMパッケージ」を作りましょう。

Yumリポジトリ登録RPMの作成

ここで作成する「Yumリポジトリを登録するRPMパッケージ」はbuild-repository-rpm.shで自動化されています。

Yumリポジトリ登録に必要なものは「リポジトリを指定するファイル」と「RPMパッケージを署名している公開鍵」の2つです。それらをアーカイブした.tar.gzがソースのパッケージを作成します。

% tar cvzf milter-manager-repository.tar.gz milter-manager.repo RPM-GPG-KEY-milter-manager

.specはこのようになります。

Summary: milter manager RPM repository configuration
Name: milter-manager-repository
Version: 1.0.0
Release: 0
License: GPLv3+
URL: http://milter-manager.sourceforge.net/
Source: milter-manager-repository.tar.gz
Group: System Environment/Base
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n)
BuildArchitectures: noarch

%description
milter manager RPM repository configuration.

%prep
%setup -c

%build

%install
%{__rm} -rf %{buildroot}

%{__install} -Dp -m0644 RPM-GPG-KEY-milter-manager %{buildroot}%{_sysconfdir}/pki/rpm-gpg/RPM-GPG-KEY-milter-manager

%{__install} -Dp -m0644 milter-manager.repo %{buildroot}%{_sysconfdir}/yum.repos.d/milter-manager.repo

%clean
%{__rm} -rf %{buildroot}

%post
rpm -q gpg-pubkey-1c837f31-4a2b9c3f &>/dev/null || \
    rpm --import %{_sysconfdir}/pki/rpm-gpg/RPM-GPG-KEY-milter-manager

%files
%defattr(-, root, root, 0755)
%doc *
%pubkey RPM-GPG-KEY-milter-manager
%dir %{_sysconfdir}/yum.repos.d/
%config(noreplace) %{_sysconfdir}/yum.repos.d/milter-manager.repo
%dir %{_sysconfdir}/pki/rpm-gpg/
%{_sysconfdir}/pki/rpm-gpg/RPM-GPG-KEY-milter-manager

%changelog
* Sat Feb 06 2010 Kouhei Sutou <kou@clear-code.com>
- (1.0.0-0)
- Initial package.

大事な部分はBuildArchitectures%postの部分です。

このYumリポジトリ登録RPMはプラットフォームに関係なく使えるのでnoarchを指定しています。

%postではRPMにも公開鍵を登録しています。

%post
rpm -q gpg-pubkey-1c837f31-4a2b9c3f &>/dev/null || \
    rpm --import %{_sysconfdir}/pki/rpm-gpg/RPM-GPG-KEY-milter-manager

このYumリポジトリ登録RPMはDebian GNU/Linux上でも作成できます。作成方法はCentOS上での方法と同じです。

% echo "%_topdir $HOME/rpm" > ~/.rpmmacros
% mkdir -p ~/rpm/{SOURCES,SPECS,BUILD,RPMS,SRPMS}
% cp milter-manager-repository.tar.gz ~/rpm/SOURCES/
% cp milter-manager-repository.spec ~/rpm/SPECS/
% rpmbuild -ba ~/rpm/SPECS/milter-manager-repository.spec

これで、~/rpm/RPMS/noarch/以下に.rpmができ、~/rpm/SRPMS/以下に.src.rpmができます。

ここで作成したRPMが冒頭で紹介したRPMです。このRPMを使うことで以下のようにパッケージをインストールできるのでしたね。

% sudo rpm -Uvh http://milter-manager.sourceforge.net/centos/5/milter-manager-repository-1.0.0-0.noarch.rpm
% sudo yum install -y milter-manager

Yumリポジトリの公開

ここまできたら、後はアクセスできる場所にアップロードするだけです。SourceForge.netではrsyncでアップロードできます。今回はhttp://milter-manager.sourceforge.net/centos/以下で公開したいので以下のようなコマンドになります。

% rsync -avz --exclude .gitignore centos/ \
    ktou,milter-manager@web.sourceforge.net:/home/groups/m/mi/milter-manager/htdocs/centos

rsyncではパスの最後の「/」の有無に注意してください。

まとめ

以上がRPMパッケージの作成と作成したRPMパッケージをYumリポジトリで公開する手順です。これら一連の手順を自動化したものはmilter managerのリポジトリで公開しています。読んでみてわかる通り、手順が多く、バージョンが上がる毎に手動で作業するのは大変です。簡単にバージョンアップに対応できるよう、自動化しておきましょう。

*1  Fedoraにはrpmdevtoolsパッケージに含まれているrpmdev-newspecで雛形を作れるようです。

*2  このバグを修正するパッチは作者に送りましたが、まだ取り込まれていません。

*3  src.rpmは~/rpm/SRPMS/以下にできます。

2010-03-03

LDD '10 Winter: メールフィルタの作り方 - Rubyで作るmilter

先日、LOCAL DEVELOPER DAY '10 WinterでRubyでmilterを作る方法について話してきました。どのタイミングでどのmilterプロトコルのコマンドが発行されるかについても説明しているので、Rubyではなく(libmilterを使って)Cでmilterを実装する場合にも参考になる部分があるはずです。むしろ、Rubyとmilterの組み合わせについて話している部分は薄めです。これは、Rubyそのものとmilterの仕組みを理解していればRubyとmilterを組み合わせることは容易だからです。

メールフィルタの作り方 - Rubyで作るmilter

少しgroongaについてもふれています。

それでは、ダイジェストで資料の内容を紹介します。完全版はリンク先を見てください。資料のPDF・ソースもリンク先にあります。

内容

話題

具体的にRubyでmilterを作る話に入る前に、まず、前提となる知識を確認します。

はじめにメールフィルタ、次にメールフィルタの仕組みの1つであるmilterについて簡単に説明します。その後、一度milterから離れてSMTPについて説明します。これはmilterの動作を理解するためにはSMTPの動作も知っておく必要があるからです。SMTPの動作を確認したらそれをふまえてmilterの具体的な動作を説明します。

ここまできたらRubyでmilterを作るための下準備は整っているはずです。実際に1つRubyでmilterを作ってみます。

ゴール

今日のみなさんのゴール

説明の途中にいくつか確認ポイントがあります。それぞれの技術は他の技術をベースになりたっているので、ベースとなっている技術をおさえていくことが、理解してしっくりくるためのコツです。

それぞれの確認ポイントをゴールとして最終的な「Rubyでmilterを作れる」ようになるゴールまでたどりついてください。

メールフィルタ

メールフィルタ

メールシステムとは外部とユーザ間でメールを配信するシステムです。すべてのメールシステムではそのままメールをやりとりするのではなく、メールを配信するまでのあいだに、メールに対してなんらかの処理を実行します。つまり、すべてのメールシステムにはメールフィルタ機能が備わっています。

MTAのプラグインとする方法

MTAにプラグイン

メールシステムでメールフィルタを実現する方法はいくつかありますが、その1つがMTA(メールサーバ)のプラグインとして実現する方法です。この方法のメリットはMTAを変更せずにメールフィルタ機能を変更できることです。milterはこのタイプで動作するメールフィルタです。

メールフィルタのまとめ

メールフィルタのまとめ

メールフィルタはメールシステムが持っている必須機能の1つです。その実現方法としてMTAのプラグインとして実現する方法があり、milterもその方法で実現されているメールフィルタです。

それでは、milterの概要について説明します。

milterについて

milter?

milterの名前の由来は「mail filter」です。milterは汎用的なメールフィルタの仕組みのため、同じメールフィルタを異なるMTAと一緒に使うことができます。

Sendmailを用いているメールシステムではmilterを利用していることが多く、milterをサポートした商用のメールフィルタも多く存在します。最近ではPostfixのmilterサポートがリリース毎に改善されていっているため、Postfixを用いたメールシステムでもmilterを利用するケースが徐々に増えています。

milterシステム

milter関連用語

「milter」は文脈によって異なるものを指すことがあります。そこで、ここでは混乱を避けるために異なる名前で呼ぶことにします。

まず、メールフィルタそのものを「milter」と呼びます。

メールフィルタとMTAは別プロセスで動作するため、プロセス間通信でフィルタ対象のメールやフィルタ結果などをやりとりする必要があります。そのやりとりのきまりを「milterプロトコル」と呼びます。

そして、「milter」と「milterプロトコル」をサポートしたMTAを含んだメールフィルタの仕組み全体を「milterシステム」と呼びます。

「milter」といった場合は「メールフィルタそのもの(ここでいうmilter)」という意味で使う場合と、「メールフィルタの仕組み(ここでいうmilterシステム)」という意味で使われる場合が多いです。「milter」という単語が使われている場合はどちらの意味かを判断できるようになってください。

milterプロトコルはSMTPと密接に関連したプロトコルです。そのため、milterプロトコルについて説明する前に、SMTPについて確認します。

SMTPの概要

簡単?

SMTPは以下の4つのコマンドが基本となるシンプルなプロトコルです。

  • HELO
  • MAIL FROM
  • RCPT TO
  • DATA

まず、「HELO」で接続したSMTPクライアントの情報を伝えます。以下の例ではSMTPサーバ(MTA)からのメッセージは先頭に「<」をつけて示します。SMTPクライアントのメッセージは先頭に「>」をつけて示します。

% telnet localhost smtp
< 220 note-pc.example.com ESMTP Postfix (Ubuntu)
> HELO localhost.example.com
< 250 note-pc.example.com

挨拶が済んだらSMTPセッションのスタートです。1つのSMTPのセッションで複数のメールを送ることができます。「MAIL FROM」、「RCPT TO」、「DATA」で1つのメールを送ります。

まず、「MAIL FROM」で送信者を伝えます。

> MAIL FROM: <kou@example.com>
< 250 2.1.0 Ok

次に、「RCPT TO」で宛先を伝えます。

> RCPT TO: <info@example.com>
< 250 2.1.5 Ok

同じメールを複数の宛先に送ることもできます。その場合は「RCPT TO」を複数回実行します。

最後に「DATA」でメールの内容を伝えます。メールの最後は「.」だけの行になります。

> DATA
< 354 End data with <CR><LF>.<CR><LF>
> Subject: Hello
> From: <kou@example.com>
> To: <info@example.com>
> 
> This is a test mail!
> .
< 250 2.0.0 Ok: queued as 054C624FB

これで、1通のメールを送信できました。続けてメールを送信する場合はまた「MAIL FROM」から始めます。

SMTPセッションを終了する場合は「QUIT」です。

> QUIT
< 221 2.0.0 Bye

これで1つのSMTPセッションが終了しました。

milterプロトコルはSMTPと密接に関わっています。それでは、milterプロトコルの詳細を説明します。

SMTPとmilterプロトコル

SMTPとmilterプロトコル

milterプロトコルにもSMTPと同じようにコマンドがあります。そして、そのコマンドはSMTPのコマンドと対応したものになっています。まずSMTPのコマンドを説明したのはそのためです。

例えば、SMTPで「HELO」というコマンドが実行された場合、「HELO」に対応する「helo」というmilterプロトコルのコマンドが発行されます。このとき、SMTPクライアントが指定したHELOコマンドの引数がmilterに渡されます。

MTAはmilterにコマンドを送った後、milterからの返答があるまでSMTPクライアントには返答しません。つまり、milterが「helo」でrejectを返すことで、SMTPクライアントの「HELO」コマンドへの返答をrejectとすることができます。これにより、MTAがSMTPレベルでできることとほとんど同じことをmilterで実現できます。

milterプロトコルのコマンドとSMTPのコマンドの対応

コマンド: メタ情報

mitlerプロトコルのコマンドはほとんどSMTPのコマンドに対応していますが、milterプロトコルのコマンドの方がより細かくなっています。例と一緒にコマンドの対応を説明します。

SMTPでの最初のコマンドは「HELO」ですが、milterプロトコルでは「helo」よりも前にコマンドが発行されます。それが、SMTPクライアントがSMTPサーバに接続したときに発行される「connect」コマンドです。

「connect」コマンド以外はSMTPのコマンドとmilterプロトコルのコマンドは1対1で対応します。「envfrom」の「env」は「envelope」の略で、「封筒」という意味です。「envfrom」で「差出人」という意味、「envrcpt」で「宛先」という意味です。「rcpt」は「recipient」の略で「受信者」という意味です。

SMTPでは1つのメールを複数の宛先に送信できます。この場合、複数回「RCPT TO」を指定します。STMPで複数回「RCPT TO」が指定されるので、milterプロトコルでも「envrcpt」コマンドが複数回発行されます。

コマンド: DATA

SMTPの「DATA」コマンドはmilterプロトコルではより細かいコマンドに分解されています。

まず、「DATA」コマンド時にはmilterプロトコルの「data」コマンドがすぐに発行されます*1。その後、SMTPクライアントはメール本体を送信しますが、「header」などのイベントはすぐには発生しません。SMTPクライアントがデータの終了を示す「.」のみの行を入力するまでは何も起きません。「.」のみの行が入力されると、MTA側でメール本文をパースして「header」、「eoh」(end of header: ヘッダーの終わり)、「body」、「eom」(end of message: メッセージの終わり)コマンドを発行します。もちろん、ヘッダーもパースしてあるので、MTAは「ヘッダー名」と「ヘッダー値」と分解した状態で情報を渡します。

このようにmilterプロトコルはSMTPと密接に関わっています。milterプロトコルのコマンドがわかれば、自分が必要な機能を持つmilterを実現するためにはどのコマンドを利用すればよいかを考えることができるでしょう。

milterサンプル: メール検索

扱うもの

説明用のサンプルとしてメール検索を実現するmilterを作成します。今回はSubject、From、Toと本文のみを扱うことにします。

メール検索を実現するために、全文検索エンジンとしてgroongaを、milterライブラリとしてmilter managerのRubyバインディングを使います。

groonga: カラム指向データストア

カラム指向

groongaは全文検索のためのインデックス作成機能だけではなく、データストアの機能も持っています。groongaのデータストアはカラム指向で、リレーショナルデータベースとは違い、レコード(行)毎にデータをまとめて持つのではなく、カラム(列)毎にデータをまとめて持っています。

このようにデータを持つと、同じカラムの複数の値へのアクセスを高速に行うことができます。このため、カラムの値を使った集計処理を高速に実行できます。集計処理とは、例えば、SQLでいうGROUP BYのような処理です。

集計処理を用いると絞り込み検索をしやすいユーザーインターフェイスを提供することができます。例えば、ショッピングサイトで商品に複数のタグがついているとします。このとき、同じタグがついている商品が何項目あるかを表示してリンクにします。1つも商品が属していないタグは表示しないようにすれば、ユーザは無駄な絞り込み操作を行わずにすみます。

全商品(123件)
タグ
  スポーツ(58件)← リンクにする
  映画(45件)    ← リンクにする
  食べ物(36件)  ← リンクにする
  旅行(0件)     ← 表示しない

この状態で「スポーツ」をクリックしたとします。

全商品(123件) > スポーツ(58件)
タグ
  スポーツ      ← 選択済みなので表示しない
  映画(26件)  ← リンクにする
  食べ物(0件) ← 表示しない
  旅行(0件)   ← 表示しない

このように、絞り込んだ後にがっかりするような操作を示さないことにより、絞り込み検索をしやすいユーザインターフェイスを作ることができます。がっかりするような操作かどうかを判断するために、同じ値を持つレコードの個数を数える、といった集計処理をしています。

groonga: バイナリパトリシアトライ

パトリシアトライ

groongaはキー管理のためのデータ構造としてハッシュテーブルとバイナリパトリシアトライを採用しています。バイナリパトリシアトライはパトリシアトライの一種です。

ここにB+木とパトリシアトライの説明を書く予定でしたが、もう、だいぶ長くなっているので省略します。また別の機会があれば紹介します。

パトリシアトライを利用すると効率よく最長一致検索を実現できます。これを試してみるためのサンプルアプリケーションを用意しました。

groongaでキーワード検出

リンク先ではキーワードを変えて試すことができます。

最長一致機能を利用してキーワード検出している部分のソースは以下の通りです。

target_text = "..."
keywords = request["keywords"].split

words = Groonga::PatriciaTrie.create(:key_type => "ShortText",
                                     :key_normalize => true)
keywords.each do |keyword|
  words.add(keyword)
end
tagged_text = words.tag_keys(target_text) do |record, word|
  "<span class='keyword'>#{word}</span>"
end

まず、パトリシアトライを作り、キーワードを登録します。Groonga::PatriciaTrieにはtag_keysという便利メソッドがあり、これを使うと「最長一致検索」→「キーワードにタグ付け」をより簡潔に記述することができます。

全体のソースはリンク先にあるソース一式の中に含まれています。

スキーマ

スキーマ: Messages

groongaのRubyバインディングであるRuby/groongaはスキーマ定義のためのDSLを提供しています。

メールを保存するMessagesテーブルにはsubjectfromtobodyカラムを定義しています。今回は簡単のため、宛先は1つのみ扱うことにしています。

スキーマ: Terms

次に、高速に全文検索を行うために索引を作成します。Termsテーブルのキーに単語(ここではbigramを利用しているので1文字か2文字の文字列)、カラムにその単語が出現するMessagesレコードのID(とN-gramなので単語の出現位置)を保持します。

subjectカラムとbodyカラムでそれぞれに対して索引を作成しています。こうすることにより、「どこかに○○が含まれているメールを検索」といった検索だけではなく、「Subjectに○○が含まれているメールを検索」、「本文に○○が含まれているメールを検索」というような細かい検索ができるようになります。細かい検索が必要ない場合はMessagesテーブルに検索対象をすべて入れたカラムを1つ作り、そのカラムに対して索引を作成してもよいでしょう。

Groonga::Schema.define do |schema|
  schema.create_table("Messages") do |table|
    ...
    table.text("text")
  end

  schema.create_table("Terms",
                      :type => :patricia_trie,
                      :default_tokenizer => "TokenBigram",
                      :key_normalize => true) do |table|
    table.index("text")
  end
end

messages = Groonga["Messages"]
from = "kou@clear-code.com"
to = "info@clear-code.com"
body = "Hello Ruby and milter!"
text = "#{from} #{to} #{body}" # <- textに検索対象をまとめる
messages.add(:from => from
             :to => to,
             :body => body,
             :text => text)

query = "Ruby"
messages.select do |record|
  record["text"].match(query) # <- textカラムで全文検索
end

Rubyでmilterを作る

Rubyでmilter

データの保存・検索の仕組みはできたので、あとは、groongaのデータベースにメールを登録するだけです。

milter managerのRubyバインディングのAPIでは、ユーザがmilterプロトコルのコマンドに対応するメソッドを定義し、ライブラリ側がそのメソッドを呼び出します。今回必要な情報はヘッダーと本文にあります。そのため、今回のmilterは以下のようになります*2

class ArchiveMilter < Milter::ClientSession
  def initialize
    @messages = Groonga["Messages"]
    @values = {}
    @encoding = nil
    @body = ""
  end

  def header(context, name, value)
    case name
    when /\A(Subject|From|To)\z/i
      key = $1.to_s.downcase
      utf8_value = NKF.nkf("-w", value)
      @values[key] = utf8_value
    when /\AContent-Transfer-Encoding\z/i
      @encoding = value
    end
  end

  def body(context, chunk)
    @body << chunk
  end

  def end_of_message(context)
    nkf_option = "-w"
    nkf_option << " -MB" if @encoding == "base64"
    @values["body"] = NKF.nkf(nkf_option, @body)
    @messages.add(@values)
  end
end

このように、Rubyでmilterを作るときは必要な処理の部分だけを記述するだけですみます。つまり、やりたいことを実現するためにどういうデータが必要で、どのタイミングでそのデータを手に入れられるかがわかれば、Rubyでmilterを作ることは簡単だということです。

登録したメールは以下のように検索・表示することができます。

query = "Ruby" # <- 検索キーワード
messages = Groonga["Messages"]
result = messages.select do |record|
  record["subject"].match(query) |
    record["body"].match(query)
end
result.sort([["_score", :desc]]).each do |message|
  puts "-" * 78
  puts "score: #{message.score}"
  puts "Subject: #{message.subject}"
  puts "From: #{message.from}"
  puts "To: #{message.to}"
  puts
  puts message.body
  puts "-" * 78
end

まとめ

Rubyでmilterを作る方法について説明しました。そのために必要な技術として、milterプロトコルの具体的な動作も説明しました。ここで説明されている内容を理解していれば、より詳細なmilter関連情報も理解しやすくなるでしょう。英語ですが、milterに関する情報はmilter.orgにまとまっています。より詳しい情報を知りたい場合はチェックするとよいでしょう。

札幌はやはりやさいい雰囲気に包まれていました。札幌Ruby会議02とは少し違う雰囲気でしたが、似ているとは感じました。

一度、札幌の人たちに会いに行ってみてはいかがでしょうか。

あわせて読みたい

  • 2010-02-14 - iakioの日記 - postgresqlグループ

    「C言語でPostgreSQLを拡張する」というタイトルで石田さんが淡々とライブコーディングされていました。会場とやりとりをしながらコーディングする様子を見ていると、札幌っぽい雰囲気を感じることができるでしょう。

*1  「data」コマンドはmilterプロトコルのバージョン4から追加されたコマンドなのでそれより古い2などを使っている場合は利用できません

*2  milter managerでは「eom」というような省略した名前を「end_of_message」という省略しない自己記述的な名前になっているので注意してください。

2010-02-15

Ruby/groonga 0.9.0, 0.9.1: 高速で使いやすい検索エンジンライブラリへ向けて

今年も肉の日がきましたね。

Ruby/groonga0.9.0がリリースされました。リリース後すぐにgroongaの新しいバージョン0.1.6がリリースされたため、0.1.6に対応した0.9.1がすぐにリリースされました。gemで自動インストールされるgroongaのバージョンが0.1.6になっているだけで、Ruby/groongaの機能は変わっていません。

いつも通り、以下のコマンドでインストールできます。システムにgroongaがインストールされていない場合は自動でダウンロードしてインストールします。

% sudo gem install groonga

全文検索エンジンgroongaの特徴はgroongaのドキュメントを参照してください。

0.9.x

前のリリースでは0.0.7だったバージョン番号が一気に0.9.1まであがっています。バージョン番号から想像できる通り、初のメジャーリリース1.0.0を意識しはじめたということです。

0.9.x系列では1.0.0に向けて以下の2点を重点的に開発していきます。

  1. より使いやすいAPIの提供
  2. 高速化

APIを改良するため、以前のバージョンとは互換性が壊れることがありますが、今後より便利にRuby/groongaを使うために、今のうちに積極的に改良していく予定です。使ってみて、「ここがこうなっていたらもっと使いやすい」、「こういうAPIがあると便利」などという意見があったらぜひお知らせください。

Groonga::Context.default#[]のショートカットとしてGroonga.[]を導入するなど、もうすでに便利なAPIの追加は始まっています。

現在のRuby/groongaにはまだ高速化の余地があります。あまり意識せずにスクリプトを書いてもなるべく高速に動作するようにライブラリ側でできることはなるべくライブラリ側で頑張る方向で開発していく予定です。ユーザが使いやすくなるように処理系が頑張るというのはRuby本体と同じ方向です。

まとめ

今年も年に一度の肉の日がきました。

Ruby/groonga 0.9.1がリリースされています。Rubyで全文検索システムを構築したい場合はRuby/groongaも検討してみてはいかがでしょうか。今のうちに改善案をだしておけばメジャーリリース時にはそれが反映されてより便利に高速な全文検索システムを構築できるようになるかもしれません。

Tags: Ruby | このエントリの Delicious history | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | | Permalink
2010-02-09

milter managerによる柔軟なメールフィルタリング: milterとは?

先日、milter managerを用いたメールフィルタリングについて話してきました。milter managerというより、milter managerがベースにしているmilterという技術についての説明の方に重点をおいています。これは、日本でのmilter情報が不足しているのを少しでも解消したいということからです。

milterはPostfixも積極的にサポートを強化している有用な技術の1つです。メールに関わっている方はその仕組みや動作について把握しておいて損はないはずです。

milter managerによる柔軟なメールフィルタリング

それでは、コメント付きでとばしとばし資料の内容を紹介します。完全版はリンク先を見てください。資料のPDF・ソースもリンク先にあります。

内容

話すこと

milterについて説明する前に、まず、より一般的なメールフィルタについてまとめておきます。その後、メールフィルタの仕組みの1つであるmilterについて説明し、最後にmilter managerも利用したmilterの使い方について説明します。

メールシステム

メールシステム

外部から届いたメールをユーザに配信するのがメールシステムです。メールシステム内部では単純に届いたメールをそのままユーザに配信するのではなく、なんらかの処理をしてから配信します。そのため、ユーザには加工されたメールが配信されます。例えば、Receivedというメールヘッダを追加したりします。

メールフィルタ

メールフィルタ

つまり、メールシステムの中にはメールフィルタの機能が内臓されています。それでは、メールフィルタについてもう少し詳しくみていきます。

メールフィルタの仕組みの種類

フィルタの仕組み

メールフィルタの実現方法にはいくつかありますが、大きく以下の3種類に分けられます。

  • MTAに組み込む方法
  • MTAのプラグインとする方法
  • フィルタMTAを挿入する方法

それぞれの方法について図で説明します。

MTAに組み込む方法

MTA組み込み

MTAに組み込む方法では、MTA内にメールフィルタの機能を内蔵させます。MTA単体で実現できます。

例えば、Postfixではaccess(5)やcidr_table(5)を用いてフィルタを適用することができます。

MTAのプラグインとする方法

MTAにプラグイン

MTAのプラグインとしてフィルタを実現する方法では、MTAと別のプロセスにメールを通すことによりフィルタリングします。この別のプロセスがフィルタプラグインです。milterもこのタイプのメールフィルタの仕組みです。

MTAとプラグインのやりとり方法はMTA独自のことがほとんどです。SendmailやPostfixなど複数のMTAで動くmilterの方が珍しいといえます。milterが複数のMTAでサポートされているのは、既に多くの有用なmilterが存在しているからと思われます。

フィルタMTAを挿入する方法

フィルタMTAを挿入する方法

複数のMTAでメールシステムを構築することもできます。フィルタMTAを挿入する方法では、外部からのメールをユーザに配信するまでの間に、フィルタリングをするMTAにメールをリレーすることで、メールフィルタリングを実現します。MTA間のやりとりにはSMTPを使うので、どのMTAでも実現できる方法です。

アプライアンス製品などはこの方法でメールフィルタ機能を提供することが多いです。

実現方法の違い

違いは?

それでは、それぞれのメールフィルタの実現方法による違いです。それぞれ特徴があるので、必要とされている機能や導入した後のメンテナンスなども考慮して、メールシステム毎に適した方法を選択することが重要です。

MTAに組み込む方法の利点はお手軽であるということです。組み込まれている機能はMTA毎に違いますが、組み込まれた機能で十分な場合はこの方法を利用するとよいでしょう。機能を追加する場合は、パッチを当てたり、バージョンアップをしたりすることになります。機能を追加する場合はメンテナンスのことをよく考えてください。

MTAのプラグインとしてメールフィルタを利用する方法の利点は、柔軟性です。多くの場合、プラグインではMTA本体が実現していることのほとんどすべてを実現できます。そのため、必要な機能をMTA本体を変更せずに追加することができます。MTA組み込み方法よりは導入が面倒ですが、システムや世間の変化にあわせたメールシステムを構築する場合は、機能追加や複数の機能を組み合わせられるこの方法を利用するとよいでしょう。

フィルタMTAを挿入する方法の利点は、どのMTAでも利用できることです。フィルタMTAを挿入する場合は、MTAが数珠つなぎになります。そのため、MTAを挿入・削除するたびに複数のMTAのリレー設定を変更する必要があり、面倒です。もし、必要な機能をすべて満たしたフィルタMTAを1つ導入するのであれば、この方法がよいでしょう。複数のフィルタMTAを導入する場合は導入・メンテナンスのことをよく考えてください。

メールフィルタの実現方法の選び方

選び方

以上のことをふまえると、以下のポイントをメールフィルタの実現方法を選択するために使えます。

  • MTAに組み込みの機能で十分な場合: MTAに組み込む方法
  • 複数の機能を組み合わせる場合: MTAのプラグインとする方法
  • 必要な機能を持ったフィルタMTAを1つだけ使う場合: フィルタMTAを挿入する方法

これからの迷惑メール対策

迷惑メール対策

メールフィルタは迷惑メール対策に利用されることが多いです。その観点から考えるとどの方法がよいでしょうか。

迷惑メールの配信方法は多様化しているため、1つの対策で完璧に防ぐことはできません。複数の手法を組み合わせて対応する必要があります。

また、これからも新しい手法で迷惑メールは送られ続けるでしょう。そうなった場合でも新しい対策を適用できることが求められます。

よって、迷惑メール対策には、複数の機能を組み合わせることが容易で、新しく機能を追加できるプラグインタイプの方法が適しているといえます。プラグインタイプの中でも、複数のMTAで利用でき、すでに多くの実装が存在するmilterがオススメです。ということで、milterの説明に入ります。

milterとは何か

milter?

「milter」といっても文脈によっては微妙に異なるものを指すことがあります。「広義の○○」「狭義の○○」といわれたりするものと同じようなものです。

「milter」はメールフィルタの仕組み全体のことを指す場合と、フィルタプログラムのことを指す場合が多いです。「milterに対応したMTA」という場合は「メールフィルタの仕組み」を指していますし、「このmilterを使う」という場合は「フィルタプログラム」のことを指しています。

milterの説明に入る前に、混乱しないように別々の名前をつけておきます。ただし、正式な名前ではないことに注意してください。あくまで、わかりやすさのためにつけた名前です。

メールフィルタの仕組み
milterシステム
MTAとフィルタプログラムが使うプロトコル
milterプロトコル
↑に準拠したフィルタプログラム
milter

それぞれの関係を図示するとこうなります。

milterシステム

milterプロトコル

SMTPとmilterプロトコル

MTAとmilter間でやりとりするmilterプロトコルはSMTPと密接に関わっています。milterプロトコルでのコマンドのほとんどはSMTPでのコマンドに由来しています。

例えば、MTAがSMTPで「EHLO」コマンドを受け取ったらmilterにも対応する「helo」コマンドが送られます。その時、EHLOで受け取ったFQDNも一緒に送られます。

また、milterプロトコルはSMTPと平行して動作するのも重要なポイントです。MTAがメッセージすべてを受信してからmilterにコマンドを送るのではないので、「helo」コマンドのときにmilter側でrejectすれば、MTAも「EHLO」コマンドのときにrejectし、「MAIL FROM」や「RCPT TO」などそれ以降のコマンドを受けとりません。

複数のmilterを使用する場合

複数のmilter利用時の動作

milterシステムでは複数のmilterを同時に利用することができます。複数のmilterを利用する場合もSMTPと平行して動作する点は変わりません。

複数のmilterを利用する場合に注意することは、それぞれのmilterはコマンド毎に順番に動くということです。1つめのmilterの処理が終わってからはじめて2つめのmilterにコマンドが送られます。

1つめのmilterの処理結果が2つめのmilterに影響を与えます。例えば、1つめのmilterで本文を変更したら、2つめのmilterには元の本文ではなく1つめのmilterが変更した本文が渡されます。DKIMなどメッセージが変更されると問題があるような処理をする場合はmilterの順序に気をつけてください。

例えば、DKIMの場合は、送信するメールの署名も受信したメールの署名の検証も一番外部に近い部分で行います。署名を行うmilterは、他のすべてのmilterが処理を終わった後に適用しなければ署名が壊れてしまうため、一番最後のmilterにします。署名を検証するmilterは、他のmilterがメッセージを変更する前に適用しなければ検証に失敗してしまうため、一番最初のmilterにします。

以上がmilterの動作です。それでは、milterの使い方について進みます。

milterシステムの気になるところ

気になるところ

milterシステムを使い込んでいくといくつか気になるところがでてきます。milterシステムはプラグインタイプなので他の方法に比べて機能の追加は容易ですが、さすがにmilterの数が増えてくると面倒になります。

milterはすべてのセッションに対して適用されます。ホワイトリストの管理は各milterの役割になります。しかし、複数のmilterを利用している場合は、複数の形式でホワイトリストを管理することになりメンテナンスが面倒になります。

これらの問題点を解決するソフトウェアがmilter managerです。

milter managerとは

milter manager

milter managerはMTA側に足りない機能を補って、milterをより使いやすくするソフトウェアです。MTA・milterどちらにも変更を加えずに導入することができるため、既存の環境を壊すことなく導入できます。

クリアコードの他のソフトウェアと同様、milter managerもオープンソースソフトウェアとして公開しているので自由に利用することができます。

milter manager導入時の構成

milter manager: 構成

milter managerはMTAとmilterの間で動作します。MTAからのmilterプロトコルのコマンドをmilterに転送し、milterからのレスポンスをMTAに転送します。MTAとmilterの間でやりとりを制御することができます。

milter managerはmilterとして実装されているため、MTAには1つのmilterとしてみえます。また、milter managerはMTA側のmilterプロトコルも実装していて、その機能を使ってmilterへコマンドを転送しています。よって、milterからはmilter managerがMTAのようにみえます。このため、MTAもmilterも変更なしでそのまま動作することができるのです。

milterシステム管理の負担を減らす

milter管理

milterシステムの設定・管理にいくつか作業が必要になります。

milterを追加する場合は、milter本体を設定した後にMTAにmilterを登録する必要があります。複数のmilterを同時に利用している場合はmilter本体の設定のみをして、MTA側に登録することを忘れてしまうこともあります。

milter managerはそのような部分を自動化し、単純なミスを防ぎます。/etc/以下を走査し、インストールされているmilterを検出したら自動で登録します。また、インストールはされているが無効にされている場合もそれを検出し、自動でそのmilterを使用しないようにします。

milterシステムは複数のMTAでサポートされていますが、完全に同じサポート状況ではなく、MTA毎に異なる部分があります。そのため、milterによってはSendmailでは動くが、Postfixでは動かないというのもありました。(最近はそのようなmilterは減ってきています。)

milter managerはMTAとmilterの間で動作します。その位置でSendmailとPostfixのmilterシステムのサポートの違いを吸収するため、同じmilterを変更せずにそのまま動作させることができます。

他にも、Sendmailはmilter毎にタイムアウトなどのパラメータを設定できるが、Postfixはmilter全体の設定なってしまうなどといった違いがあります。このような違いも、milter managerがmilter毎にパラメータを管理することにより、PostfixでもSendmailと同じようなmilterシステムサポートを利用することができます。

milterシステム管理のまとめ

milter管理のまとめ

このようにmilter managerを導入することにより、milterシステムの管理が負担を減らすことができます。milter managerはMTAとmilterの間に入って動作するため、MTA・milterのどちらも変更せずにmilterシステムのサポートをより充実させることができます。このため、メールシステム全体の管理の負担も減ります。

柔軟なmilter利用

柔軟性: ユーザ毎の設定

前述の通り、milterはすべてのメールに対して適用されるため、milter全体でホワイトリストを共有したり、ユーザ毎に適用するmilterを選択することが面倒になります。それぞれのmilterにまたがって設定をする必要があります。

milter managerを使うとこの問題を解決することができます。MTAはすべてのmilterを適用しますが、milter managerはSMTPセッションの情報を使って動的に適用するmilterを選択する機能を提供しています。また、LDAPやRDBなどからもデータを取得することができるため、メールシステム外にあるアカウントシステムと連携した制御をすることもできます。

この機能を使うことにより、milter全体で共有するホワイトリストやユーザ毎にどのmilterを適用するかということをmilter managerレベルで一括管理できます。これにより、ユーザからの要望や利用状況に合わせた柔軟なメールシステムをメンテナンスしやすい構成で運用できます。

tarpit問題

tarpit問題: ケース例

前述の通り、milter managerはmilterシステムをより使いやすくする機能を提供します。しかし、それだけにとどまらず、さらにメールシステムをより使いやすくする機能も提供します。その1つがtarpit問題の解決方法の提供です。

tarpitとはSMTPクライアントへのレスポンスを(RFCの範囲内で)わざと遅らせることにより、すぐに切断してくる迷惑メール送信SMTPクライアントからのメールを受信しないようにする迷惑メール対策手法です。

迷惑メール送信側はタイムアウト時間をRFCで示されている時間よりも短めに設定していることが多いというデータがあります。これは、できるだけ短時間で多くの迷惑メールを送信したいというのが理由ではないかと考えられています。この特徴にあわせた迷惑メール対策手法がtarpitです。

tarpitを使うことにより多くの迷惑メールを受信せずにすみますが、同時SMTPセッション数が増加し、リソース消費量が増えるという問題があります。1〜2分程度のレスポンス遅延を導入することにより、2倍程度セッション数が増えるというデータもあります。

この問題は、SMTPクライアントが切断したらすぐにSMTPセッションを終了することにより緩和することができます。Postfixなどでtarpitを実現すると、SMTPクライアントの切断を検出せずに、指定した時間までSMTPセッションを維持してしまいます。そのため、必要以上に同時SMTPセッション数が増加してしまうのです。

tarpit問題の解決

解決法2: milter manager

milter managerにはSMTPクライアントが切断したらすぐにSMTPセッションを終了する機能がついています。

milter managerはmitlerプロトコルでMTAからSMTPクライアントのソケット情報を受け取っています。それとnetstatコマンドの結果*1を照合することによってMTA外からSMTPクライアントの切断を検出します。切断を検出したらMTAとmilter双方に処理の中止を通知することですぐにSMTPセッションを終了することができます。

まとめ

柔軟な迷惑メール対策

milterをベースとした柔軟なメールフィルタリングを実現するための仕組みを説明しました。

メールフィルタリングには以下のような仕組みがあり、それぞれ特徴があります。

  • MTAに組み込む方法: お手軽
  • MTAのプラグインとする方法: 柔軟
  • フィルタMTAを挿入する方法: 汎用的

新しい迷惑メール対策にも柔軟に対応していくメールシステムにはプラグインタイプがオススメです。プラグインタイプの仕組みの1つとしてmilterを説明しました。

milterはSMTPと密接に連携している仕組みです。milterプロトコルについても説明し、milterがどのような流れでフィルタリングを行うかも示しました。すでに多くの迷惑メール対策手法がmilterとして実現されているため、今すぐmilterを使って迷惑メール対策を組み込んだメールシステムを構築することができます。

最後に、milterシステムをより使いやすくするソフトウェアとしてmilter managerを紹介しました。milter managerを導入すると、設定の自動化や一元管理によりメンテナンスが容易なメールシステムが構築できることを説明しました。

組織に合わせたメールシステムを構築する場合があれば参考にしてみてください。

*1  Linux上では/proc/以下の情報を利用することも考えられます。

2010-02-05

最新
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|