sendmailはメールの仕分けを行う。このプログラムには外部からのメールも内部からのメールも送り込まれる。sendmail は宛先アドレスに応じてメールを適切に仕分けする任務を負っている。
sendmail によってメールはユーザのメールボックスに届けられるか、あるいは、インターネットのどこかのホストへ配送される。
sendmail は仕分けを変換規則に基づいて行う。この変換規則は /mail/lib/rewrite
に記述されている。
以下に sendmail が処理するデータとメールの仕分け先を簡単に図示する。
arisawa@ar.aichi-u.ac.jpではなく
ar.aichi-u.ac.jp!arisawaと表現する。
tcp!ar!80 net!tcp!arなどである。UUCP 形式はこれらの形式と良く合致している事が理由かも知れない。 sendmail はもっと複雑なアドレスを処理する必要がある。
@A,@b:alice@C
@A:alice@C@b
alice@C@b@A
A!b!C!alice
ホスト名 local はメールを受け取ったホストを意味している。このホスト名が ar であれば
local!alice
は
ar!alice
と等価である。
/mail/lib/rewrite
を中心とした幾つかのファイルである。正規表現を使用するメリットは、強力である事、そして(正規表現を知っている者にとっては)新たな知識を必要としない事である。
以下に正規表現に馴染みの無い読者を対象に必要な限りに於て正規表現を解説する。sendmail が扱う正規表現には sendmail 独自の拡張が含まれているので正規表現を知っている読者も目を通してほしい。
正規表現は文字列の集合を表現する。
メールアドレスに現われる文字列は ASCII の印字文字(`!'- `~')の集合である。従って sendmail が扱う正規表現ではこの文字集合を基礎にする。この文字集合を基礎文字集合と呼ぶ。正規表現の記号 `.' は基礎文字集合を表す。
文字集合は列挙する形式でも表現される。そのためには記号 `[' と `]' が使用される。[!@] は2つの文字 `!' と `@' から構成される文字集合である。[ ] の中の先頭に使用される記号 `^' は否定を表す。[^!@] は2つの文字 `!' と `@' を含まない文字集合、即ち、`.' から [!@] を差し引いた文字の集合である。
メールアドレスは英字の大文字と小文字を区別しない。そこで sendmail が扱う正規表現も同様にこの区別を行わない。 従って正規表現 alice は "alice", "Alice", "aLice", ... 等の 32 個の文字列の集合を意味する。
和集合は記号 `|' で表現される。正規表現 alice|bob|carol は3つの正規表現 alice, bib, carol の和集合であり、合計 32 x 8 x 32 個 の文字列を要素とする集合である。
文字集合に続く記号 `*' はその文字集合に含まれる文字が 0 個以上続く事を意味している。記号 `+' は `*' とよく似ているが、文字が 1 個以上続く事を意味している。 従って[^!@]+ は `!' と `@' を含まない長さが 1 以上の文字列の集合である。他方 [^!@]* には長さが 0 の文字列が含まれる。
文字列sが文字列集合Sに含まれる時にマッチすると言う。
文字列集合S が正規表現rによって表現されている時、文字列 s と正規表現 r はマッチすると言う。
正規表現の Postmaster には文字列 pOsTmAsTer も postmaster もマッチする。また
正規表現 .* には任意の文字列がマッチする。
文字列の置換を扱う場合には記号 `(' と `)' が役に立つ。正規表現に含まれる ( ) で囲まれた範囲が置換の際に指定できるのである。 文字列 local!alice は 正規表現 local!(.*)にマッチする。 この場合 (.*) の部分は alice とマッチしている。
記号 ( ) は複数使用できる。 文字列 alice@ar.aichi-u.ac.jp は 正規表現 ([^@]+)@([^@]+) にマッチする。 この場合最初の([^@]+)は alice とマッチしており、第二の([^@]+)は ar.aichi-u.ac.jp とマッチしている。
正しいアドレスではないが文字列 alice@@@ar.aichi-u.ac.jp は正規表現 (.+)@(.+) にどのようにマッチしているのだろうか?
この場合には最初の (.+) は alice@@ にマッチし、第二の (.+) は ar.aichi-u.ac.jp にマッチする。ルールは次の様になっている。
最初の ( ) は残りの文字列のマッチングが妨げられない範囲で最大の文字長を選択する。次の ( ) もまた同様な原則でマッチする範囲が定められていく。
正規表現の中に ( ) が使用されている時、マッチした文字列を \1 、\2 ... で表す。 各々、最初の ( ) にマッチした文字列、第二の ( ) にマッチした文字列 ... を意味する。
正規表現の中に ( ) が使用されていなくてもマッチした文字列は記号 `&' で表される。例えば文字列 bob は 正規表現 [^!@]+ にマッチする。この場合 & は bob を表す。
正規表現で使用される特殊文字を本来の意味に使う時には、その文字の前に '\' を付ける必要がある。例えば文字 `.' は 正規表現では \. で表現する。
sendmail の変換規則に実際に使われる事は無いであろうが、正規表現ではさらに以下の文字が特殊な意味に使われる:
以上で sendmail の変換規則は理解できる。
/mail/lib/rewrite
に記述されている。
rewrite
を眺めて見よう。
---------------- /mail/lib/rewrite---------------------- # case conversion for postmaster Postmaster alias postmaster # local mail [^!@]+ translate "/bin/upas/aliasmail '&'" local!(.*) >> /mail/box/\1/mbox \l!(.*) alias \1 (ar|ar.aichi-u.ac.jp)!(.*) alias \2 # we can be just as complicated as bSD sendmail... # convert source domain address to a chain a@b@c@d... @([^@!,]*):([^!@]*)@([^!]*) alias \2@\3@\1 @([^!]*),@([^!@,]*):([^!@]*)@([^!]*) alias @\1:\3@\4@\2 # convert a chain a@b@c@d... to ...d!c!b!a ([^@]+)@([^@]+)@(.+) alias \2!\1@\3 ([^@]+)@([^@]+) alias \2!\1 # /mail/lib/remotemail will take care of gating to systems we don't know ([^!]*)!(.*) | "/mail/lib/qmail '\s' 'net!\1'" "'\2'" ---------------------------------------------------------このファイルに於て、最初のフィールドは送信アドレスを表している。送信アドレスの多様なパターンは正規表現によって表現されている。ここに現われる \l (エル)はホスト名を表している。
第二のフィールドは4つの値をとり、それらは処理の種類を表している。即ち、
/mail/lib/rewrite
に記述されたルールに従って再帰的に変換され、最終的には ">>" または "|" による処理を受ける。(そして終わる。)
変換規則は一般に 3 つのフィールドを持つが、第二フィールドに "|" が使用された場合にのみ第四フィールドを許す。第四フィールドの役割は極めて特殊である。
(外部へのメール配送を行う /mail/lib/qmail
の実行を合理的に行うために使われている。)
/mail/lib/rewrite
はサンプルとしての性格を持ち、厳格さを犠牲にしている事に注意する。例えば
\l!(.*) alias \1 (ar|ar.aichi-u.ac.jp)!(.*) alias \2のペアは次の様に一行で書く事もできる。
(\l|\l\.aichi-u\.ac\.jp)!(.*) alias \2
[^!@]+
にマッチし、変換規則
[^!@]+ translate "/bin/upas/aliasmail '&'"
/bin/upas/aliasmail bob
[^!@]+
にマッチした文字列、この場合は bob を意味する事を思い出そう。)この後 2 つのケースが考えられる。bob が別名ファイルに登録されている場合とされていない場合である。
登録されている場合には
/bin/upas/aliasmail bob
は bob の別名、例えば bob@plan9.bell-babs.com
を出力する。
登録されていない場合には local!bob
を出力する。
これらの各々のケースに応じて送信アドレスは bob@plan9.bell-babs.com
または local!bob
に置き換えられる。
bob@plan9.bell-babs.com
宛ての alice の メールは正規表現([^@]+)@([^@]+)
にマッチし、変換規則
([^@]+)@([^@]+) alias \2!\1
plan9.bell-labs.com!bob
に変換される。そしてこのアドレスは正規表現 ([^!]*)!(.*)
にマッチする。従ってこのメールは変換規則([^!]*)!(.*) | "/mail/lib/qmail '\s' 'net!\1'" "'\2'"
/mail/lib/qmail alice net!cse.psu.edu 9fans
local!(.*)
にマッチする。従って変換規則
local!(.*) >> /mail/box/\1/mboxが適用される。 この場合
\1
は bob なので、このメールは /mail/box/bob/mbox
に追加される。
alice@ar.aichi-u.ac.jp
は正規表現 ([^@]+)@([^@]+)
にマッチし、変換規則
([^@]+)@([^@]+) alias \2!\1
ar.aichi-u.ac.jp!alice
に変換される。そしてこのアドレスは
正規表現 (ar|ar.aichi-u.ac.jp)!(.*) にマッチし、変換規則
(ar|ar.aichi-u.ac.jp)!(.*) alias \2
\2
は alice を意味しているのでアドレスは alice に変換される。この後は bob へのメールと同様な経過を辿る。
@([^@!,]*):([^!@]*)@([^!]*) alias \2@\3@\1 @([^!]*),@([^!@,]*):([^!@]*)@([^!]*) alias @\1:\3@\4@\2によって処理される。
@([^@!]*),([^!@,]*):([^!@]*)@([^!]*) alias @\1:\3@\4@\2
@A,@b,@C:alice@D
アドレス @A,@b,@C:alice@D
は正規表現 @([^!]*),@([^!@,]*):([^!@]*)@([^!]*)
にマッチし、
\1 = A,@b \2 = C \3 = alice \4 = Dである。 従って第二の行に従って
@A,@b:alice@D@C
@([^!]*),@([^!@,]*):([^!@]*)@([^!]*)
にマッチし、
\1 = A \2 = b \3 = alice \4 = D@Cである。 従って第二の行に従って
@A:alice@D@C@b
@([^@!,]*):([^!@]*)@([^!]*)
にマッチし、
\1 = A \2 = alice \3 = D@C@bである。従って第一の行に従って
alice@D@C@b@A
([^@]+)@([^@]+)@(.+) alias \2!\1@\3 ([^@]+)@([^@]+) alias \2!\1によって Plan9 の標準形式に変換される。
A,b,C,D をホストのアドレス、alice をユーザ名として、アドレス
alice@D@C@b@A
がどのように処理されるかを追って見よう。
アドレス alice@D@C@b@A
は正規表現 ([^@]+)@([^@]+)@(.+)
にマッチし、
\1 = alice \2 = D \3 = C@b@Aである。 従って第一の行に従って
D!alice@C@b@A
([^@]+)@([^@]+)@(.+)
にマッチし、
\1 = D!alice \2 = C \3 = b@Aである。 従って第一の行に従って
C!D!alice@b@A
([^@]+)@([^@]+)@(.+)
にマッチし、
\1 = C!D!alice \2 = b \3 = Aである。 従って第一の行に従って
b!C!D!alice@A
([^@]+)@([^@]+)
にマッチし、
\1 = b!C!D!alice \2 = Aである。従って第二の行に従って
A!b!C!D!alice
------------------------------- qmail -------------------------------- #!/bin/rc sender=$1 shift addr=$1 shift qer /mail/queue mail $sender $addr $* && { runq /mail/queue /mail/lib/remotemail/dev/null >[2=1] & exit 0} --------------------------------------------------------------------このスクリプトには明示的な変数 (sender と addr) と引数の残りを表す変数 $* が含まれている。
変数 sender はメールの差出人のメールアドレスである。差出人を alice とせよ。alice が ローカルホストのユーザであれば sender の値は単に "alice" である。他方このメールが他のホスト例えば同一ドメインの venus から発送されて来た場合には sender の値は "venus!alice" となるが、外部のドメインの research.att.com から発送されて来た場合には "research.att.com!alice" となる。
変数 addr はメールの発送先のホストのアドレスである。 alice がメールを bob@plan9.bell-labs.com に出した場合には addr の値は "plan9.bell-labs.com" になる。そして "bob" は qmail の残りの引数 $* で与えられる。
読者は気付いているかも知れないが、qmail は誰のメールでも指定されたアドレスへ発送する。即ち他のホストから来たメールも発送するのである。これはリレーホスト(メールホスト)としての設定である。近年 SPAM が流行しておりリレーホストは SPAM に悪用されている。悪用されるのがイヤなら qmail に手を加えよう。
------------------------------- qmail -------------------------------- #!/bin/rc sender=$1 shift addr=$1 shift if(test -d /mail/box/$sender) qer /mail/queue mail $sender $addr $* && { runq /mail/queue /mail/lib/remotemail/dev/null >[2=1] & exit 0} if not cat > /dev/null --------------------------------------------------------------------こうすればメールボックスを持っているローカルなユーザだけがメールを外へ出す事ができる。
筆者の qmail はもっと複雑である。
筆者は自宅に ISDN ルータを備えている。自宅からメールを出す場合にメールの発信アドレスが自宅のマシンになっていると困るのだ。メールがリターンされた場合に受け取るマシンが存在しないのである。自宅からメールを出した場合にもメールを大学のマシン ar から出した事にしておけば問題は解決する。(自宅のマシンのメールホストを ar に設定する事。)
------------------------------- qmail -------------------------------- #!/bin/rc sender=$1 shift addr=$1 shift OK=0 if(~ $sender mars!*){ ifs0=$ifs ifs='!' s=`{echo -n $sender} ifs=$ifs0 sender=$s(2) switch($sender){ case kenji sender=arisawa } OK=1 } if(test -d /mail/box/$sender) OK=1 if(~ $OK 1){ awk ' /^From |^From: |^received:|^Message-Id:|^Date:|^To:|^received:/{f=1;next} /^[ \t]/ && f==1 {next} /^$/{ while (getline > 0) print $0} /.+/{f=0;print $0} ' |\ qer /mail/queue mail $sender $addr $* && { runq /mail/queue /mail/lib/remotemail/dev/null >[2=1] & exit 0} } if not{ echo '-------------' $sender $addr $* '-------------' >> /sys/log/junk cat >> /sys/log/junk } --------------------------------------------------------------------ここで
if(~ $sender mars!*){の
mars!*
の部分は自宅のマシンに付けた名前に依存する。(筆者の場合は実際には複数のマシンを処理している。)
switch($sender){でユーザ名を変換している。(筆者の場合には家庭のマシンでのユーザ名は kenji であるが、大学のマシンでは arisawa である。複数の家族が利用する場合にはもっと複雑になるはずだ。)
SPAM に対してはそれを捨てないで
echo '-------------' $sender $addr $* '-------------' >> /sys/log/junk cat >> /sys/log/junkで記録を採っている。