Sieveちょう使える!

世間では仕分けが話題になっているようなので、私もprocmailを使用してメールの仕分けを行おうかなーなんて思って試してみたところ、どうやら残念ながら研究室のメールサーバではそれが出来ないようなシステム構成になってるようだってことが分かってとても残念だった。代わりにsieveなるものが使用できるということがメールのログ見てて分かったのでためしに使ってみたところ、これが非常に使い勝手がいいのね。さすが未来の標準。ただ問題は資料がRFCしかないことで、これじゃあ誰も使い始めない。
せっかくなので、私がちょっと使った感じの記録を簡単な解説としてここに残しておこうと思う。文法とかそのへんを、RFC5228に準拠して説明するよ。

全体的な設定

簡単な設定ファイルは、こんな感じになる。

require ["fileinto", "reject"];

if address :contains "From" "lab-ml@example.org" {
        fileinto "INBOX.lab";
}
elsif header :matches "Subject" "[Super-ML *" {
        fileinto "INBOX.SuperML";
}

見た感じで分かると思うけれど、

  • require文
  • あとはif-elseの滝

という構成でメールを処理していく。

文法は、

 <control>    <test>        <action>
 (ifとか)  (headerとか)   (discardとか)

という非常に簡素な作り。

で、これを適当なファイル(ここでは.sieve_text)に書いたら、sievecなるコンパイラを利用してコンパイルする必要がある。私の環境ではこんなの:

$ /usr/local/cyrus/bin/sievec .sieve_text .sieve

コンパイルが通り、特にメッセージもなく.sieveが生成されたらOK。もう動く*1

control

はじめに書く要素。if, require, stopの三種類がある。stopはよくわからないので略。

require

requireは、require "hoge"; を何行も書いても構わないし、上記のようにrequire ["hoge", "fuga"]; と複数項目をまとめてもよい。
Sieveではディレクトリ移動とか破棄とかのアクションを基本要素として考えておらず、これらはrequireによって導入されると言うことになっている。その考え方に本当に意味があるのかは多分誰にも分からない。
なお、requireされていない機能を使おうとした時は、スクリプト自体起動しないことが処理系に求められている(MUST)。
fileinto, reject, envelope, encoded-character の四つ*2RFC内では定義されている。

if

ifは、もはや説明するまでもないと思うので、RFCより文法だけコピペ。

Usage:   if <test1: test> <block1: block>
Usage:   elsif <test2: test> <block2: block>
Usage:   else <block3: block>

test

testは条件判断を司る文法要素。address, header, exists, envelope, sizeと言った条件判断に加え、allof, anyof, not, true, false と言った論理要素もこれに含まれる。

header
Usage:   header [COMPARATOR] [MATCH-TYPE]
         <header-names: string-list> <key-list: string-list>

メールのヘッダの行で、たとえば

X-Caffeine: C8H10N4O2

という行があったとすると、その行に対するマッチングが

header :is "X-Caffeine" "C8H10N4O2"
header :contains "X-Caffeine" "8H1"

などとすることで行える。マッチングはglob(いわゆるワイルドカード)のみ対応しているので、たとえばexampleメーリスに該当するのは

if header :matches "Subject" "*[example-?????]*" {
    discard;
}

のようになる。*3

address
Usage:   address [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE]
         <header-list: string-list> <key-list: string-list>

実のところ、header testを使えばAddressのマッチングもだいたい行えてしまう。"To","From","Cc","Bcc"なんかも当然ながらヘッダ要素だから、それをマッチング対象に指定すればheader testでも問題なく利用できる。
addressが独自に持っているのは[ADDRESS-PART]という部分。ここは ":localpart" ":domain" ":all" から選択できる。デフォルトは":all"。localpartやdomainを指定した場合、アドレスとして正しくないものはマッチされなくなる。
この分類はlocalpart@domainという具合に該当すると思われるけど、未確認。

if address :domain :contains ["To", "Cc", "Bcc"] "*lemon-crazy*" {
    discard;
}
exists
Usage:   exists <header-names: string-list>

要素が存在するかどうかのチェック。

if not exists ["From","Date"] {
    discard;
}
envelope
Usage:   envelope [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE]
         <envelope-part: string-list> <key-list: string-list>

Envelope To及びEnvelope Fromを評価出来る。"TO"とか"FROM"とかで指定する。*4
SMTP MAILで評価されるFROM*5、及び SMTP RCPTで評価されたTO*6、を条件に使える。

size
Usage:   size <":over" / ":under"> <limit: number>

特筆すべき点は無し。over,underなので、「ちょうどその大きさ」の表現はここにはないってくらいかな。もし表現したいなら、面倒でも論理を使って表すことになる。

allof, anyof, not
Usage:   allof <tests: test-list>
Usage:   anyof <tests: test-list>
Usage:   not <test1: test>

論理演算。たとえば、こんな使い方もあるかもっ!?

if anyof ( allof ( not size :over 100K,
                   size :over 2K),
           exists "X-Caffeine"){
    keep;
}else{
    discard;
}

2Kより大きくて100K未満のメッセージ、またはX-Caffeine入りのもののみ通し、あとは捨てる。

true, false
Usage:   true
Usage:   false

trueは必ずtrueに評価される。falseは必ずfalseに評価される。

match type

:is :contains :matches の三種類が定義されている。名前から分かる通り、

:is 一致
:contains 部分一致
:matches マッチ(glob)

というもので、さきほどのtestのときに使われた。
globのマッチングは、?の一文字ワイルドカードと*のワイルドカードを使ってマッチングする。カッコをエスケープせずに使えるのがなかなか便利なのだけど、やはり正規表現と比較すると表現力が弱くて、ときどきもにょる。

regex extension

<draft-murchison-sieve-regex-07>に、正規表現に関する書き方が記述されてる。これが実装されているかどうかはもちろん処理系依存

require "regex";

# Try to catch unsolicited email.
if anyof (
  # if a message is not to me (with optional +detail),
  not address :regex ["to", "cc", "bcc"]
    "me(\\+.*)?@company\\.com",

  # or the subject is all uppercase (no lowercase)
  header :regex :comparator "i;octet" "subject"
    "^[^[:lower:]]+$" ) {

  discard;     # junk it
}

"regex"をrequireし、match typeの一種として ":regex" を指定。あとはPosix準拠の正規表現の実装が求められてる。
これ以上は処理系依存なので略。

action

actionはメールに対して行う手続き。fileinto, redirect, keep, discardの四種類がある。

アクション 何をするか 備考
fileinto ディレクトリ移動 メールの仕分けに
redirect redirect このメールだけ転送、とか出来る
keep フィルタ通過 default 条件に合わなかった場合はこれ
discard 捨てる このコエダメがァーァ!

同じ条件のところに複数のアクションを書いておけば、転送しつつ通常通り受信など行うことも出来る。

if true {
   fileinto "INBOX.mailinglist";
   redirect "suu-g@example.org";
   keep;
   discard;
}

みたいに。つまり複製かもーん。
なお、この場合discardは無意味。

文の最後にはセミコロンを付けるのを忘れずに。コンパイラがチェックしてくれるけど。

そしてあなたの.sieveへ

現在のところ、RFCでの共通定義はこれくらい。あとは使用しているMTAによって独自実装があるかも知れないし、逆に未実装要素があるかもしれない。
そんなこんなで、あなたのsieveスクリプトが出来上がるわけだね。

<未実装>

*1:もちろんMTAの設定は必要

*2:Vacationっていうのもあるけれど、これはジョー

*3:?及び*しかRFCに書かれてない…

*4:case insensitive;大文字小文字関係ない

*5:reverse-pathが空の場合はADDRESS-PARTにかかわらず空文字列とマッチ

*6:間近の、自分に配送された時のもの