特定ネットワークにむけた
sendmailによるリレーメールサーバ


(WIDE-CFのANOTHER_ADDRESSをm4で)
sasaki@nobug.tukusi.ne.jp
update:


1. 目的


通常、インターネットメールを交換するメールサーバは、
というように動作します。
ところが、ドメインを複数持つ組織では、メールの扱いを特殊にしたい場合があります。
たとえば、次のような場合です。

このような配送を行う場合、WIDEプロジェクトのCFによる記述では、ANOTHER_ADDRESS という機能がありました。これを利用すると、上記の問題が解消されます。
しかし、WIDEによるCFは、sendmail-8.9.xを最後にサポートされなくなりました。そこで、この機能をsendmail付属のcfによっ て実現してみます。

また、上記機能に加えて実験的な機能も加えてみることにします。それは、
という機能です。この機能を持ったメールリレーサーバを既存ネットワークに追加すると、既存メールサーバを変更せずに、他組織サブドメインからのメールを 受け取ることができます。メールアドレスをMTAが勝手に書き換えてしまうことには議論があるようですが、既存メールサーバをほとんど変更しないで追加で きると、なにか嬉しいことがあるかもしれません。

次のようなネットワーク環境を想定しています。


既存メールサーバとのインターネットの間に、ここで作成するリレーサーバをはさみます。
リレーメールサーバは他組織ネットワークにも接続され、他組織メールサーバが見えている、という前提です。

なお、これからいくつかのサンプル定義を示しながら記述しますが、sendmail.cf の
 lhs [TAB] rhs [TAB] comment
という並びに気をつけてください。
ここにあるサンプルをそのままコピーしても、多分 [TAB]であるべきところが正しくありません。



2. 特定アドレス向けのFROM 書き換え


CFで実現されるANOTHER_ADDRESSは、次のような仕組みで実現されています。
  1. 新しい smtp2 メーラを用意する
  2. 特定アドレス向けの配送メーラとなるsmtp2は、mailertable機能により選択するよう設定する
  3. smtp2のS=ルールに、アドレス書き換えルールを定義する
ここでも、同様の手法をとります。新しいメーラ arelay を定義します。

Marelay    P=[IPC], F=mDFMuX, S=AnotherRelay, R=EnvToSMTP, E=\r\n, L=990,
               T=DNS/RFC822/SMTP, M=1000000,
               A=TCP $h



続いて、アドレス書き換えのルール定義です。
sendmail-8.10.x からは、メーラに対して、エンベロープ書き換えルール/ヘッダ書き換えルールを定義できるようになりました。メーラに指定されるS=env/hdrがその ルールです。ひとつだけ記述すると、どちらにも影響するルールとなります。ここでは両方書き換えることにしますので、ルールはひとつだけでかまいません。

他組織ネットワークへ行くメールはすべてのFromを書き換えてしまって問題ないのですが、それではあまりに芸がないので、自組織のFromアドレスを 持ったものだけを書き換えることにします。これを行うためには、書き換えるべきアドレスのリストを作り、それに合致したFromアドレスに限って書き換え るようにします。新しいマクロを2つ定義することにします。

D{anotherdomain} 書き換えられる他組織ドメイン名
C{anothermasq}  書き換え対象とするFromドメイン名

sendmailのマクロは、Dは単純な文字列定義、Cはクラスと呼ばれる、文字列リストの定義です。書き換えられる他組織ドメイン名は多分唯一ですが、 対象にするFromドメインは複数あるかもしれないのでこのようなマクロ定義を行っています。
これらのマクロを利用して、次のようなアドレス書き換えルールを作成します。

SAnotherRelay
R$+<@$={anothermasq}$*>         $@ $1 @ ${anotherdomain}        rewrite
R$+<@$={anothermasq}>           $@ $1 @ ${anotherdomain}        not dot
R$*                             $@ $1                           not change



ここで記述した書き換えルールは、arelayメーラのS=に定義します。



3. 特定ネットワークからのTo書き換え


Toを書き換えるために、既存サーバへ送るメールも、新しく定義するメーラを通すようにします。デフォルトで選択されるesmtpなどのルールを書き換え てもいいのですが、別定義したほうが自由度が増します。lrelayというメーラを新しく定義します。

Mlrelay    P=[IPC], F=mDFMuX, S=EnvFromSMTP/HdrFromSMTP, R=Rewrite_another, E=\r\n, L=990,
               T=DNS/RFC822/SMTP,
               A=TCP $h



続いて、アドレス書き換えのルール定義です。
ここでは、Fromアドレスを基準に考えるのではなく、特定の経路から入ったメールを対象にすることにします。特定の経路は、このサーバに接続してきた相 手先のIPアドレスで認識することにします。
なぜこのような手法と取るかというと、ここで受け取るメールのToアドレスは他組織のものとなっており、Fromアドレス書き換えルールとの競合によって 配送メーラはarelayが選択されてしまい、メールが戻ってしまうからです。そこでまずIPアドレスを基準にして特定経路からであることを調べ、 envelope toを自組織に書き換えてmailertableによるメーラ決定がlrelayになるように仕向け、その後にヘッダのメールアドレス書き換えを行いま す。

sendmailのマクロ中には、接続してきた相手先アドレスを示す{client_addr}というマクロが定義されています。これを利用して、接続し てきたサーバが指定ネットワークのアドレス範囲(または相手そのもの)かどうかを判断し、そうであればToを書き換える、ということにします。相手サーバ のアドレス範囲を定義するマクロを追加定義します。

D{anothernet} 別経路であるネットワークアドレス

これも相手サーバが複数の別ネットワークアドレス系であることを考慮し、クラスで定義しています。

これらのマクロを利用して、次のようなアドレス書き換えルールを作成します。
まず配送経路の認識は、ルールセット98を利用します。これは、mailertableによるメーラ選択をlrelayに仕向けるため、 mailertableの評価の前に、envelope to を書き換えてしまわなければならないからです。

SParseLocal=98

#####################################
#  from another_relay
#####################################
R$*                     $: [$(dequote "" $&{client_addr} $)] $1         [IP] addr
R[$={anothernet}]   $* $@ $>Rewrite_another $2         rewrite ip
R[$={anothernet}.$-] $* $@ $>Rewrite_another $3         rewrite net
R[$*] $*                $@ $2                           delete [IP]
R$*                     $@ $1                           return addr

SRewrite_another
R$+ < @ $={anothermasq} $* >    $@ $1 < @ $m . >        another --> localdomain
R$+ < @ $={anothermasq}  >      $@ $1 < @ $m . >        not dot
R$*                     $@ $1                           not change


この中にはサブルーチンとしてのRewrite_anotherというルールも定義します。これはlrelayによってTo:アドレスを書き換えるための ルールとしても利用されます。このルール中では、書き換えられるアドレスとして、$mを用いています。もし別のアドレスにする必要がある場合は、新しく定 義してもよいでしょう。

ここで記述した書き換えルールは、lrelayメーラのR=に定義します。



4. m4マクロとしてのまとめ


こうしてできたメーラとルールのセットを、m4マクロとして記述しておけば、mcによる作成に対応することができて便利です。ここで作成したものを、m4 マクロに記述し、cf/hackディレクトリに例えばanother_relay.m4という名前で格納しておけば、

HACK(another_relay)

という記述をmcに追加することで、独自作成した機能が利用できるようになります。

m4マクロを記述する場合、どのルールをどこへ置くかが設定できなければ、意図したところへルールを記述することができません。これにはdevert() という嬉しい機構が用意されています。

devirt()マクロは、どこへ配置するかを決める番号を持っています。sendmailでは、これを次のように利用しています。(今回利用するところ しか記述していません)

devirt(-1) コメントとして展開しないように指示
devirt(0)  そのまますぐ展開するように指示
devirt(3)   メーラ定義に展開するように指示
devirt(7) ParseLocal(ルールセット98)へ展開するように指示
  
これらを利用すると、ローカルルールセットや、メーラを、正しい位置に配置できます。

なお、これらのdevirt()マクロを利用しなくとも、.mcファイルに直接記述する方法もあります。
LOCAL_CONFIG, MAILER_DEFINITIONなどがその例ですが、ここでは取り上げません。

最終的にできあがったanother_relay.m4は、次のようになります。

define(`_ANOTHER_RELAY_', `')
divert(0)
VERSIONID(`$Id: another_relay.m4,v 1.0 2003/06/26 07:26:14 sasaki Exp $')
divert(3)
#####################################
#  from another_relay
#####################################
R$*                     $: [$(dequote "" $&{client_addr} $)] $1         [IP] addr
R[$={anothernet}] $*    $@ $>Rewrite_another $2         rewrite addr
R[$={anothernet}.$-] $* $@ $>Rewrite_another $3         rewrite net
R[$*] $*                $@ $2                           delete [IP]
R$*                     $@ $1                           return addr

SRewrite_another
R$+ < @ $={anothermasq} $* >    $@ $1 < @ $m . >        another --> localdomain
R$+ < @ $={anothermasq}  >      $@ $1 < @ $m . >        not dot
R$*                     $@ $1                           not change
divert(7)
#####################################
#  Another Relay Route
#####################################
SAnotherRelay
R$+<@$={anothermasq}$*>         $@ $1 @ ${anotherdomain}        rewrite
R$+<@$={anothermasq}>           $@ $1 @ ${anotherdomain}        not dot
R$*                             $@ $1                           not change
#####################################   
#  Special relay mailers
#       Use with smtp.m4
#####################################
Marelay         P=[IPC], F=mDFMuX, S=AnotherRelay, R=EnvToSMTP, E=\r\n, L=990,
                T=DNS/RFC822/SMTP, M=1000000,
                A=TCP $h
Mlrelay,        P=[IPC], F=mDFMuX, S=EnvFromSMTP/HdrFromSMTP, R=Rewrite_another, E=\r\n, L=990,
                T=DNS/RFC822/SMTP,
                A=TCP $h
divert(0)





5. mcの記述


通常sendmail.cfを作成するための.mcには、追加定義したマクロと、HACK(another_relay)の記述があれば利用できます。

サンプルのmcは次のようになります。

divert(0)dnl

FEATURE(`mailertable',`hash -o /etc/mail/mailertable')dnl
FEATURE(`virtusertable',`hash -o /etc/mail/virtusertable')dnl
FEATURE(`access_db', `hash -o /etc/mail/access')dnl
FEATURE(`use_cw_file')dnl
FEATURE(`accept_unresolvable_domains')
dnl # this is for localrelay
dnl define(`MAIL_HUB', lrelay:[192.168.1.25])
dnl # this is another relay mailer
HACK(`another_relay')

MAILER(local)dnl
MAILER(smtp)dnl

dnl # this is another relay mailer definition
### Another Routings
#       anotherdomain   masq for another route MAIL FROM:
#       anothermasq     masq target domain for another route
#       anothernet      network address on another route
D{anotherdomain}another.net
C{anothermasq}main.net another.net
C{anothernet}192.168.2





6. mailertableの記述

配送メーラを指定するために、ここではmailertableを利用するので、その記述を忘れてはいけません。
mailertableでは特定配送先に向けて、ドメインが引けなくても配送できるように[IP]での直接指定をするといいかもしれません。
また、自組織宛てについてもToを書き換えるのならば、lrelayメーラが選択されるように記述しておく必要があります。

次のような記述になります。

another.net        arelay:[192.168.2.25]
main.net            lrelay:[192.168.1.25]


mailertableに自組織を記述しない場合は、MAIL_HUB機能を利用し、ローカル配送になるものをすべて既存メールサーバへ送るように設定す ることもできます。(5.mcの記述でコメント化して記述してあります)
MAIL_HUB機能を利用する場合は、このホストが自組織として認識するドメイン名を/etc/mail/local-host-namesに記述して おく必要があります。次はlocal-host-namesの例です。

localhost
localhost.main.net
mx.main.net
main.net


7. おまけ

7.1. WIDE版CFmailertable拡張への対応

sendmail付属のcfによるmailertableでは、

any.domain ドメインに正確にマッチしたものはこのメーラを使う
.any.domain
配下のドメインにはこのメーラを使う
(any.domainそのものはマッチしない)

という記述が可能です。

WIDE版CFでは、これに加え次の拡張機能が定義できます。

+any.domain
トップも含めた配下ドメインにはこのメーラを使う
(any.domainそのものもマッチする)

これを行うためには、ルールセット90(SMailertable=90)に、次の行を加えることで対応できます。

###################################################################
###  Ruleset 90 -- try domain part of mailertable entry         ###
###################################################################
SMailertable=90
### for WIDE extension
R$*<$-.$+> $*           $: $1 < $(mailertable +$2.$3 $) > $4   challenge +entry
R$*<+$-.$+> $*          $: $1 <$2.$3> $4                                 restore if match
###
R$* <$- . $+ > $*       $: $1$2 < $(mailertable .$3 $@ $1$2 $@ $2 $) > $4
R$* <$~[ : $* > $*      $>MailerToTriple < $2 : $3 > $4         check -- resolved?
R$* < . $+ > $*         $@ $>Mailertable $1 . <$2> $3           no -- strip & try again
R$* < $* > $*           $: < $(mailertable . $@ $1$2 $) > $3    try "."
R< $~[ : $* > $*        $>MailerToTriple < $1 : $2 > $3         "." found?
R< $* > $*              $@ $2                           no mailertable match


最初の行で、mailertableに+エントリがあるものを確認します。
次の行で、それにマッチしたものは、+再びを取り外します。
これによって、一度目の評価は、記述されているドメイン部分も検索対象になります。
追加していない場合(デフォルトのMailertableルール)では、記述されているドメイン部の.最初のドットまでをはずしてmailertable から検索するようになっていますので、配下のみが対象になります。上記2行は、その最初をドメインそのものに変更してやることになります。

ルール90は、m4/proto.m4に直接記述されています。
出来上がったsendmail.cfを書き換えて使うのが面倒であれば、proto.m4を書き換えてしまう、という手法をとることもできるでしょう。そ の際、次のような記述をしておくと、拡張機能を使う/使わないを、mcの定義によって変更ができるので便利かもしれません。
proto.m4 の書き換え例と、sendmail.mcの定義例をあげておきます。


proto.m4の書き換え例 (ルールセット90の部分)

###################################################################
###  Ruleset 90 -- try domain part of mailertable entry         ###
dnl input: LeftPartOfDomain <RightPartOfDomain> FullAddress
###################################################################
SMailertable=90
dnl shift and check
dnl %2 is not documented in cf/README
ifdef(`_MAILER_TABLE_CF_EX_', `dnl
### CF extension of mailertable + entry
R$*<$-.$+> $* $: $1 < $(mailertable +$2.$3 $) > $4
R$*<+$-.$+> $* $: $1 <$2.$3> $4 cf + extension
###',
`dnl')

R$* <$- . $+ > $*       $: $1$2 < $(mailertable .$3 $@ $1$2 $@ $2 $) > $4
dnl it is $~[ instead of $- to avoid matches on IPv6 addresses
R$* <$~[ : $* > $*      $>MailerToTriple < $2 : $3 > $4         check -- resolved?
R$* < . $+ > $*         $@ $>Mailertable $1 . <$2> $3           no -- strip & try again
dnl is $2 always empty?
R$* < $* > $*           $: < $(mailertable . $@ $1$2 $) > $3    try "."
R< $~[ : $* > $*        $>MailerToTriple < $1 : $2 > $3         "." found?
dnl return full address
R< $* > $*              $@ $2                           no mailertable match',
`dnl')



sendmail.mcの例

define(`_MAILER_TABLE_CF_EX_')dnl   # これを定義すると拡張が利用できる

FEATURE(`mailertable',`hash -o /etc/mail/mailertable')dnl

FEATURE(`virtusertable',`hash -o /etc/mail/virtusertable')dnl
FEATURE(`access_db',`hash -o /etc/mail/access')dnl
FEATURE(`use_cw_file')dnl
FEATURE(`accept_unresolvable_domains')
dnl # this is for localrelay
dnl define(`MAIL_HUB', lrelay:[192.168.1.25])
dnl # this is another relay mailer
HACK(`another_relay')
MAILER(local)dnl
MAILER(smtp)dnl
dnl # this is another relay mailer definition
### Another Routings
#       anotherdomain masq for another route MAIL FROM:
#       anothermasq masq target domain for another route
#       anothernet network address on another route
D{anotherdomain}another.net
C{anothermasq}main.net another.net
C{anothernet}192.168.2

R[$={anothernet}]  $* $@ $>Rewrite_another $2         rewrite ip
R[$={anothernet}.$-] $* $@ $>Rewrite_another $3         rewrite net



この文書が最初に記述されたのは2003/06/30です
N, Sasaki <sasaki@nobug.tukusi.ne.jp>