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
control
はじめに書く要素。if, require, stopの三種類がある。stopはよくわからないので略。
require
requireは、require "hoge"; を何行も書いても構わないし、上記のようにrequire ["hoge", "fuga"]; と複数項目をまとめてもよい。
Sieveではディレクトリ移動とか破棄とかのアクションを基本要素として考えておらず、これらはrequireによって導入されると言うことになっている。その考え方に本当に意味があるのかは多分誰にも分からない。
なお、requireされていない機能を使おうとした時は、スクリプト自体起動しないことが処理系に求められている(MUST)。
fileinto, reject, envelope, encoded-character の四つ*2がRFC内では定義されている。
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は無意味。
文の最後にはセミコロンを付けるのを忘れずに。コンパイラがチェックしてくれるけど。