Sieveが良いとは限らない?

Sieveではprocmailのようにパイプによるスクリプトの起動などが出来ないっぽい。「そんな、メールの振り分け言語にパイプの標準規格なんか入れる必要ないだろガハハ!」って感じでしょうか。
標準規格のこういうカタブツなところは困ったものです。ユーザごとのスパム対策は.forward + maildropとかgetmail使うとかする必要があるようで、なんかSieve使うためにその辺使うのって本末転倒というか、そのあたり使うならそもそもSieve使う必要ないんじゃないかと疑問に思わずにはいられないような気がしないでもないこともないではない。

And, unlike most other mail filtering script languages, it does not allow users to execute arbitrary programs. This is particularly useful to prevent virtual users from having full access to the mail store. The intention of the language is to make it impossible for users to do anything more complex (and dangerous) than write simple mail filters.

http://wiki.dovecot.org/LDA/Sieve

上の記述は、動作にroot権限を利用しがちなdovecotの場合なので、必ずしも他の場合に適用できるわけではないけれど、Sieveのツボはついてるんじゃないかな。「フィルタ言語」以上でも以下でもない、ユーザのディレクトリに配送する最後の振り分け機能としての役割を果たすものである、と。


ちなみに、前回、extensionであると説明した正規表現は、cyrusでもdovecotでもCMUでもちゃんと対応しているらしい。もちろん実装は環境依存なのでそれぞれの説明書見るしかない。

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:間近の、自分に配送された時のもの

opensolarisで名前解決したいじゃんね

nwamを止めて再起動したらlo以外のinterfaceが見えなくなって、非常に焦った。冷静に、

# ifconfig bge0 plumb
# ifconfig bge0 dhcp

などしたところ、特に問題もなく再取得できた。

・・・かと思われたが、なんだか名前解決が出来ない。resolv.confの編集だけではいけなかったらしい。
http://opensolaris.org/jive/thread.jspa?threadID=89035 に従った結果、

(# create your /etc/resolv.conf)
# cp /etc/nsswitch.dns /etc/nsswitch.conf
# svcadm enable svc:/network/dns/client:default
# svcadm restart system/name-service-cache:default

で解決出来ることが分かった。
この例ではコピーしているけれど、本来であれば /etc/nsswitch.conf の hosts: の項目に dns を加えて、system/name-service-cache:default を再起動すれば良い。
ところで ipnodes: は hosts: のエイリアスらしい・・・。

opensolarisでdhclientしたいじゃんね

opensolarisでdhclientの仕方が分からず困ってたのも遠い過去のよう。やり方が分かったらもうopensolarisでnwamを必要とすることがなくなって世界*1が平和になった*2
やれやれ、僕はshこれでようやくまともに使える。

こうやる。*3

# ifconfig <interface> dhcp

分かってしまえば超単純。どうしてこれが分からなかったのか分からないくらい簡単。簡単すぎて誰も教えてくれなかった。需要ないのかなnwam無しのdhcpって。

nwamを切るところから追うと、

# svcadm disable /network/physical:nwam
# svcadm enable /network/physical:default
(# /sbin/dhcpagent)
# ifconfig <interface> dhcp

という感じ。ps aux | grep dhcp とかで見てみてdhcpagentが停止しているようだったらifconfig前で起動する。

dhcpagentは、dhcpのクライアント側の動作を制御するdaemon。リースし直しなどを管理してくれる。どのインターフェイスdhcpに設定するかはifconfigコマンドで指定する。
このあたりはFreeBSDとかLinuxとかとは異なっていて、ちょっと面白い。dhcpにも全体で管理プログラムが動いているあたり、osolらしいような気がする。

*1:userland

*2:当社比

*3:ただしdhcpagentが起動していることが必要

東京を歩こう

東京には空はないと智恵子は言ったし、世界は美しくなんてないってキノも言った。地下鉄に乗って大学と家とを往復するだけのまともな日常の中では東京なんてただのどうしようもなく混雑した街でしかないけれど、でも一歩踏み出してみて歩き回ってみると、この東京へは世界中の人たちが観光へ来ているだけのことはあるってことがわかるはず。いや、世界の人たちだけじゃない、むしろ東京(または黄蓋郊外)に住んでいるからこそ楽しめる街の作りがある。

…とか日記を書こうと思ったのだけど、夜にiPhoneでとっただけの写真は載せるにしのびないようなひどい写り方をしたものしかなかったため、断念。写真なしのコース説明だけするので、よほど物好きな人以外は読んでも面白くないものが以下続くよ。起承転結とかないし、笑いとか特にないですから。単なる散歩の記録。

西小山から先のルートを載せてみた。うちからだと27.5kmの距離だった。
http://www.chizumaru.com/route/alongroad/?routeid=9fe3ccf9eca94c29a3e3efbf376d02d0

続きを読む

これはいいピアノ

http://hamusoku.com/archives/92927.html山崎まさよしがとても良かったので、mp3全部落とした。・・・普通に聞くのと同程度の負荷のはずだよね?

$ wget http://hamusoku.com/archives/92927.html && cat 92927.html | perl -ne 'if(/embed.*?src="(.*?mp3)"/){`wget $1`;}'

一文で実行するとしたら、上記のようになると思う。


wgetの標準出力記事を見てなおした。
http://jijixi.azito.com/cgi-bin/diary/index.rb?date=20080214

$ wget -q http://hamusoku.com/archives/92927.html -O - | perl -ne 'if(/embed.*?src="(.*?mp3)"/){`wget $1`;}'

うん。いいんじゃないかな。

メモ

bashで制御文字をecho

$ /bin/echo -ne "aa\x8"

/bin/echoを使うこと(ビルトインと/bin/echoとで違うことがある)、ダブルクオートを使うこと、-neオプションを使うこと、バクスラの後はxを使う(16進の場合)こと、がポイントのよう。罠が多すぎる!


これ使うと、たとえばカウントダウンが出来たりしてたのしい。