tcpserver | ||
---|---|---|
HOME |
同じく D.J.Bernstein 作の qmail を研究している時に出会った、 TCP クライアント-サーバコネクション制御ユーティリティ。 xinetd (inetd) と tcp-wrappers の機能を併せ持ち、特定のサービスへのコネクションの同時成立数を規制したり、クライアントを制限したり、コネクションの成立と同時に目的のデーモンへ環境変数を渡したりできる。起動スクリプトを少し工夫すれば、通常 xinetd 経由でしか起動できない様々なデーモンを、安全に rcスクリプトから起動させることが可能だ。
tcpserver を含むパッケージ。 |
個別に入手することも可能(だったが今はほとんどリンク切れ)。 netqmail-1.05 あるいは 1.06 のソースの other_patches ディレクトリに含まれているのでそれらを使用する。
glibc errno | glibcが 2.3.1 以降の場合必須。つまり、最近のほとんどの Linux リリースでは、これを当ててコンパイル時のマクロ指定を更新してやらないとコンパイルできない。 |
ucspi-rss または ucspi-tcp-0.88.a_record |
ucspi-tcp 付属プログラムのひとつ rblsmtpd を改良する。 SMTPデーモンと併せて使用する可能性があるなら当てておいたほうがいい。 rblsmtpd は、RBLチェックプログラム (リンク集を参照のこと)。qmail-1.03 開発時点では、不正ホスト情報をテキストで提供するRBLが多かったが、現在ではDNSのAレコード問い合わせ式が主流。このパッチは、rblsmtpd を Aレコード問い合わせに対応させる。 |
ucspi-tcp-0.88.a_record ucspi-tcp-0.88.nobase |
上記 ucspi-rss を当てない場合 (Aレコードサーバを使わない場合) にのみこの2つを適用すべし。 rblsmtpd がデフォルトで問い合わせに行く RBL rbl.maps.vix.com は、もはやテキスト形式をサポートしていない。 nobaseパッチは、 -r オプションが明示されない場合にもそこを見に行こうとしていた rblsmtpd の挙動を止めさせる。 |
1. ソースの展開
user$ tar xzvf ucspi-tcp-0.88.tar.gz
2. パッチ当て
user$ cd ucspi-tcp-0.88 user$ patch -p1 < /path/to/the/patch
3. コンパイルとインストール
user$ make user$ su root# make setup check
tcpserver のルールファイルは、 tcp-wrappers の hosts.allow, hosts.deny と、 xinetd の設定ファイルを併せたような役割をする。まず、テキストファイルにディレクティブを書き、それを tcprules というコマンドでバイナリフォーマットの cdb データベースファイルに変換、そのファイルを tcpserver 起動時に -x オプションで読み込ませることで利用する。
どこに置いても構わないのだが、筆者の慣例で、 root 所有の /etc/service ディレクトリに置くという仮定で解説を進める。
ルールは、
[address]:[instruction]
というフォーマットの1行で成る。行頭から行末まで、スペースを紛れ込ませてはいけない。ルールが複数行あれば、上から見ていって一番先にマッチしたルールが適用される。
USER@=HOSTNAME あるいは USER@IP のようにクライアントのユーザ名やホスト名を用いることも可能だが、インターネットサーバで利用する上では、ユーザ名を知るには互いに IDENTサービスポートを開けている必要があって現実的でないし、信用できる形でホスト名を利用するには接続の度に 2回の DNS問い合わせ (正引きと逆引き) が発生することになるので滅多に使わない。よって、ここでも IPアドレスのみを取り扱うことにする。
IPアドレスは、ドットやハイフォンを使ってワイルドカード的な指定や範囲指定もできる。以下に例を示す;
192.168.1.1: | ずばり 192.168.1.1 そのもの |
192.168.1.: | 192.168.1.0 から 255 |
192.168.1.1-14: | 192.168.1.1 から 14 |
192.168.2-15.: | 192.168.2.0 から 192.168.15.255 |
: | アドレスを無しにすると全てのアドレスにマッチ |
address にマッチした時に適用する指示。 allow (許可する)、 deny (拒否する) の他に、目的のプログラムへ送る環境変数を定義することもできる。Linux で標準的な env に例えれば `env LANG=en man syslog' のようなことをやっているのだと考えることができる。例を示す;
allow,RELAYCLIENT="" | 許可するとともに環境変数 RELAYCLIENT をデーモンに渡す |
allow,VARA=%hoge%,VARB=/foo/ | カンマ区切りで複数指定することも可能。また、クォートは " である必要はなく、前後を同じ記号で挟みさえすればよい |
tcprules コマンドを使う。 tcprules は STDIN から入力を読み取るので、通常はリダイレクトを使用する。
tcprules 変換後cdb名 変換時tempファイル名 < ルールテキスト名
実際の作業例:
root# cd /etc/service root# tcprules tcp.smtp.cdb tcp.smtp.tmp < tcp.smtp
tcpserver [option] [host_or_IP] [port] [program]
主なオプション | 意味 |
---|---|
-u -g | 目的のプログラムを起動する際の UID, GID を指定 |
-x | 読み込むルールファイル (cdb) を指定 |
-H | 相手アドレスの DNS名前解決をしない。つまり $TCPREMOTEHOST を取得しようとしない |
-R | 相手ユーザ名を問い合わせない。つまり $TCPREMOTEINFO (IDENT情報) を問い合わせない |
-l localname | サーバ自身のホストネームを DNS 検索せず、代わりに localname で指定した名前を「リゾルブ済み」のものとして解釈する。余計な DNS 検索を抑制できる。引数には、よく localhost またはそれと同義な 0 (ゼロ) を指定する |
-c n | 同時に成立できるコネクションの最大数を n 本に制限。デフォルトは 40 |
-v | Verbose モード (エラーやステータスをプリント) |
接続を受け入れる自分のホスト名または IPアドレス。つまり、通常は、接続を受け入れるネットワークインターフェースを指定する意味合いを持つ。例えば localhost と指定すると 127.0.0.1 への要求だけに応ずるし、/etc/hosts ファイルで定義してある自分のホスト名を指定すれば、そのインターフェースからの要求にだけ応じる。 0 にすると全てのインターフェースで待ち受けることになり、実際的にはそうすることが多い。
待ち受けるポート。ポートナンバーそのものか、 /etc/services ファイルで定義されたサービス名を指定する。 0 にすると、空いているポートを使う (滅多にやらない)。
tcpserver が接続を受け入れた暁に実行する、目的のプログラム。 tcpserver そのものは常駐するようにはできていないので、末尾に & を付けてバックグラウンドジョブ化する必要がある (そうでない例を筆者は今のところ知らない)。
この章では、主に、通常 xinetd 経由で起動されるようなデーモンを、 tcpserver を使って init.d の rcスクリプト (SysVinit) で起動させる形に切り替えた、 RedHat Linux や Fedora Core での実例を紹介する。目的はそれぞれだが、 rc起動にすると
といった利点がある。
これを忘れると、tcpserver で起動しようとした時に "Address already in use" というエラーが出て起動できない。 RedHat 系のディストリビューションでは、 xinetd 経由で呼び出されるプログラムの規定は、 /etc/xinetd.d/ 下の、そのサービスをファイル名とした各ファイルで定義されている。 rcスクリプト起動に切り替える場合には、当該のファイルの中の
disable=no
を
disable=yes
に変更した上で、xinetd をリスタートする必要がある。
サーバアプリケーション自体がロギング能力を備えている場合には頓着する必要はないが、そうでない場合には、 tcpserver 起動文の最後でロギングユーティリティへ出力をパイプ渡ししてやる。渡し先のユーティリティは、Linux標準の logger を使う方法と、 qmail に付属する splogger を使うやり方がある。
firebird の接続ログを daemon ファシリティの info レベルで syslog へ送る例
tcpserver -v ... ... 2>&1 | logger -p daemon.info -t firebird &
tcpserver -v ... ... 2>&1 | splogger telnetd 10 &
& の手前の 10 は、ファシリティを数字で表したもの (10 は authprivファシリティ)。省略も可能で、省略した場合のデフォルトは 2 つまり mail ファシリティとなる。数字表記のログファシリティコードと mail, secure, daemon といった "名称" との対応は /usr/include/syslog.h で定義されている。
tcpserver 経由でバックグラウンドプロセス化したプログラムは、 ps の捉えるコマンド名がどれもこれも "tcpserver" となるためか、 daemon ファンクション (/etc/rc.d/init.d/functions スクリプトで定義されている) では PID が特定できず、 PID ファイル /var/run/xxx.pid の書き出しに失敗してしまうことがある。その結果、一応起動はできても、いざ "service xxxx stop" や "/etc/rc.d/init.d/xxxx stop" で止めようとしても、PID ファイルがないため killproc 関数が動作せず、デーモンが停止できないという状況に陥る。これは、tcpserver コマンド直後に ps の出力を awk や grep でフルイに掛けて自力で PID を見つけ、その値を PIDファイルへ書き出せば解決できる。
ただし、そうしたルーティンを一個一個の rcファイルに書くのは無駄が多く、ミスにもつながるので、筆者は detectpid という別スクリプトファイルにして、 様々な rcスクリプトから利用している。以下に紹介する rcスクリプトでは、 detectpid スクリプトを /usr/local/bin/ へコピーして実行ビットが立ててあることが必須条件になっている。
※ 2007/1/9 detectpidスクリプトを更新し (Ver.2.0)、引数の数の制限をなくした。何かの都合で古いバージョンが必要な人のために Ver.1.0 も一応保持しておく。ただし 1.0 では引数 (検索文字列) は常に 2つでなくてはならない点に注意。
detectpid {psから探し出すためのフィルタ文字列1} [フィルタ文字列2] [フィルタ文字列3...]
戻り値: PID
例) qmail-smtpd の場合:
detectpid qmail-smtpd tcpserver
ps の仕様やバージョンによるのか、ps の出力形態が多少異なることがあるため、最適なフィルタ文字列は違ってくるかもしれない (例えば RedHat 7.2 と FedoraCore 1 とでは違った)。例えば "tcpserver" と "smtp" を与えてみるなど、いくつかトライしてもらえば PID は必ず探知できるはずだ。なお、引数は正規表現で与えることもできる。ただしその場合、引数が AWK に渡る以前にシェルの解釈を受けるということを頭に置いて、正規表現特殊文字は上手くエスケープしてやらなければならない。
qmail の解説ページでみっちりと説明しているので、そちらを参照していただきたい。もちろん detectpid が必須。
起動スクリプトの中核、つまり tcpserver を telnet ポート要求に対して待機させて、要求が来たら telnet サーバ実行ファイルを呼ぶようにさせるコマンドは、下記のようになる。
tcpserver -H -R -l 0 -v -c 5 \ -x /etc/service/tcp.telnet.cdb 0 telnet \ in.telnetd 2>&1 | /var/qmail/bin/splogger telnetd 10 &
chkconfig 対応の rcスクリプトの完成版 がこれ。もちろん detectpid が必須。
tcpserver コマンド部分は以下のようになる。
tcpserver -H -R -l 0 -v -c 5 \ -x /etc/service/tcp.ftp.cdb 0 ftp \ vsftpd 2>&1 | /var/qmail/bin/splogger ftpd 10 &
vsftpd の場合、クライアントのアドレスに応じて動作をダイナミックに変えることもできる。 vsftpd はプロセス毎に環境変数 VSFTPD_LOAD_CONF にセットされている主設定ファイルを読み込むので、下のような tcpserver ルールを用意しておけば;
127.0.0.1:allow 192.168.0.:allow :allow,VSFTPD_LOAD_CONF="/etc/vsftpd/vsftpd.conf.anon"
外からの接続ではアノニマス、 LAN内部からの接続ではシステムユーザのログイン、といった具合に動作を切り替えられる。完成例を置いておくが、vsftpd 自体の設定も絡んでいて複雑なので、必ず VSFTPDのページも一読願いたい。もちろん detectpid が必須。
起動部分は下記のようになる。
tcpserver -H -R -l 0 -c 50 -u firebird -g firebird \ -x /etc/service/tcp.firebird.cdb 0 gds_db \ fb_inet_server 2>&1 | logger -p daemon.info -t firebird &
rcスクリプト化したものはこれ。もちろん detectpid 必須
tcpserver の基本動作はディスクリプタ 0 でネットワークからの入力を読み取り、ディスクリプタ 1 でネットワークへ出力するというものなので、簡単なシェルスクリプトを組んで、特定のポートで TCP コネクション待ち受けてクライアントへ某かのレスポンスを返すサーバプログラムを作成することもできる。例えば、特定の TCP ポートへの疎通試験ツールとしての用途が考えられる。試しに作成したのが、下記の実に簡易なサーバスクリプトだ。
multiserver@ は、以下のようなコールの仕様とした;
multiserver@ [-p LISTEN_PORT] {start|stop|list}
引数 | アクション |
LISTEN_PORT | 待ち受けポート (数字での指定のみ想定)。省略した場合には multiserver@ スクリプト内で定義した $DEFAULTPORT (デフォルトは 23 つまり telnet) で待ち受ける。 |
start | LISTEN_PORT で待ち受ける tcpserver プロセスを生成する。 |
stop | LISTEN_PORT で待ち受けている tcpserver プロセスに TERM シグナルを送る、つまり停止する。 |
list | 現在待機している tcpserver プロセスの待ち受けポート番号をリスト表示する。-p オプションは、指定したとしても無視される。 |
上記を少し改良して、クライアント - サーバ間での連絡に使えるようにしたのがこの tcpresponder ツールだ。下記のファイルで構成されている。
準備:
実行:
[tcpresponderサーバ]
--SysVinit版--
/etc/init.d/tcpresponder [-p LISTEN_PORT] {start|stop|status}
通常は `-p 待ち受けポート' は不要で、DEFAULTPORT が使われる。-p オプションはパートタイム的に他のポートでも上げられるように残しただけだ。PIDファイルやロックファイルはポート番号毎に作られるようにしたので、同時に上げることも可能。「準備」でも書いたように、respondercmd で定義していないクライアントIP から接続が来たり、メッセージの頭に `TCPRESPONSE=' が付いていなかったり(ナンチャッテプロトコルですナ)、メッセージが空だった場合には、即座に接続を切り、その旨をログに残す。
tcpserver は、クライアントからの接続が確立すると、引数のプログラムを起動し、ネットワークからの入力をプログラムのディスクリプタ 0 へ注ぎ込み、プログラムからディスクリプタ 1 への出力をネットワークへ送る。respondercmd スクリプトはそのことに基づいて書いてある。respondercmd の `# Here, do whatever you want. Below is a sample procedure.' 以降はお好みの処理に書き換えて使っていただきたい。
--Upstart版--
{start|stop|status} tcpresponder [port=LISTEN_PORT]
引数として、この他に tcpcmd=COMMAND や tcpopt="tcpserver_OPTION " を指定して挙動を変えることもできる。ただし、異なるポートで待つインスタンスを幾つも同時に立ち上げたい場合は、/etc/init/tcpresponder.conf を次のように書き換える必要がある。
#start on started rc RUNLEVEL=[2345] <--自動起動しないようコメントアウト stop on started rc RUNLEVEL=[S016] instance $port <--これを挿入する console output script [ -f /etc/sysconfig/tcpresponder ] && . /etc/sysconfig/tcpresponder : ${tcpopt:=$TCPOPT} : ${port:=$DEFAULTPORT} : ${tcpcmd:=$TCPCMD} /usr/local/bin/tcpserver $tcpopt $port $tcpcmd end script
上記のように複数立ち上げ用に書き換えた場合は、Upstart の特性上、起動時に port=xxx だけは必ず付けなくてはならない(だから自動起動はできない)。
[tcpnotifyクライアント]
tcpnotify MESSAGE TO SEND
tcpclient は、サーバとの接続が確立すると、引数のプログラムを起動し、プログラムからディスクリプタ 7 への出力をネットワークへ送り、ネットワークからの入力をディスクリプタ 6 で受け取る。tcpsend はそれに基づいた非常に単純なシェルスクリプトだ。サーバからの返答を受け取る必要がなければ tcpsend の中の `cat <&6' は削除してしまっても構わない。tcpnotify はメッセージの頭に必ず `TCPRESPONSE=' を付けてからサーバへ送信する。