サーバー側では必ず bindを実行して、ソケットを指定の
ポートと接続していましたが、クライアントでは bindは実行しませんでした。
なぜでしょう。それはクライアント側での connect や sendto の際に、OSが
クライアント側のポートを自動的に適切に割り当ててくれるからです。
クライアント側に自動的に割当たられるポート番号は規約により1025番以降です。
OSはこの1025から順番に bind を試みて、空いているポートに bindします。
他方、Unix domainの通信の場合はクライアント側の Unix domain
ソケットの名前は空でありません。
普通はクライアント側のポート番号や UNIX domain ソケットを気にする ことはありませんが、特殊な事情により特定の番号のポートや特定の PATHの UNIX domain ソケットを使ってクライアントにネットワーク通信を させたいことがあります。そのような場合に、クライアント側で bind を 明示的に実行します。
Local domain の場合は以下のようにして connect もしくは sendto の 前に bind を実行して指定の UNIX domain ソケットから発信するようにします。
Internet domainの場合は以下のようにして connect もしくは sendto の 前に bind を実行して指定のポートから発信するようにします。#define CLINET_SOCKNAME "/tmp/c_udsock" unlink( CLIENT_SOCKNAME ); sockaddr_un c_addr = SOCKADDR_UN_INIT( AF_LOCAL, CLIENT_SOCKNAME ); bind( s, (sockaddr*)&c_addr, sizeof(c_addr) );
もちろん、このポート番号が空いていないとなりません。 一般ユーザーは 1024番以下のポートへの bind は禁止され、 5001番以降のポートへの bind が推奨されています。#define CLIENT_PORT htons(5001) sockaddr_in c_addr = SOCKADDR_IN_INIT( AF_INET, CLIENT_PORT, InAddr(htonl(INADDR_ANY)) ); if( bind( s, (sockaddr*)&c_addr, sizeof(c_addr) ) < 0 ){ perror("clinet bind"); exit(1); }
さて、このようにクライアント側のポートを自分の好きな値にすることに 何の意味があるのでしょうか? 直前にも言及しましたが 一般ユーザーは1024番以下のポートに bindすることはできません。 逆に言えば root 権限ならば 1024番以下の空いているポートに bindすることが できるのです。これを特権ポートと呼びます。 一般ユーザーがクライアントプログラムを実行して そのプロセスがroot権限を持つように set uid bit を立てておくのです。
root権限を持つプロセスのプログラムというのは プログラマが細心の注意を払って作るものです。実行しているユーザーが誰なのか 調べたり、本当にその人なのかを確認したりします。 なのでrootのプロセスがネットワーク通信での他のマシンのプロセスに対して 言うことはきっと信用できると思われます。もちろんそのマシンの管理人が 信用できることが前提ですが。
これを逆に見れば、1024番以下のポートからクライアントがアクセスして きたのなら、これはroot権限をもつプロセスであり、もしそのクライアントホストが 信頼できるホストなら、そこのrootのプロセスは信頼でき、 このネットワーク通信でクライアントが通達してくることは 信用できる。という論理になるのです。
この信頼できるホストのリストというのは /etc/hosts.equiv です。 このファイルには、このサーバーが信頼するリモートのマシンと リモートのユーザー名が以下のような形式で列挙されています。
2番目の例はホスト reliable.somewhere.com の全ユーザーを信頼するということです。nicehost.somewhere.com niceguy reliable.somewhere.com
クライアントが果してこの信頼できるリストに載っているかどうかのチェックは iruserok関数が一手に行ってくれます。その使い方は後で説明します。
このようにリモートのクライアントを信頼して、そこのrootプロセスの ネットワーク通信を信頼して、種々の特別なサービスを提供するのです。 その例としては rlogin, rsh, rcp, popperのRPOP, youbinのメールヘッダー 通知機能、などがあります。
特権ポートをbindするようなクライアント側の関数は以下のようになるでしょう。
1024以下と言いましたが、実際に特権ポートとして使うことが推奨されているのは 600番から1023番までのようです。#include int bind_privileged( int s ) { sockaddr_in c_addr = SOCKADDR_IN_INIT( AF_INET, htons(0), InAddr(htonl(INADDR_ANY)) ); for( u_short port=600; port<1024; port++ ){ c_addr.sin_port = htons(port); if( bind( s, (sockaddr*)&c_addr, sizeof(c_addr) ) == 0 ) return 0; if( errno != EADDRINUSE ){ return -1; } } errno = EADDRNOTAVAIL; return -1; }
サーバー側で特権ポートからの接続をacceptする関数は以下のようになるでしょう。 /etc/hosts.equivで信頼できるホストからのアクセスのみを許すことにします。 ユーザー名はチェックしないことにします。
iruserok関数の引数 "nobody" はちょっとしたズルです。本当はここには クライアントが通知してくるユーザー名を指定するべきなのです。でも 今はホストのユーザーごとのチェックはしないことにしているので これでいいのです。int accept_privileged( int sb ) { sockaddr_in c_addr; socklen_t len = sizeof(c_addr); int s; if( (s=accept( sb, (sockaddr*)&c_addr, &len )) < 0 ) return -1; u_short port = ntohs(c_addr.sin_port); if( port<600 || 1023
iruserok関数の第1引数にクライアントのIPアドレスを u_long 型で与えます。 第3引数はクライアント側のユーザー名で、 /etc/hosts.equiv や .rhosts 中に 記述されたユーザー名と照合するために使われます。 第4引数はサーバー側のユーザー名で、サーバーのホームディレクトリの .rhosts のパスを作るために使われます。 第2引数に0を与えると、iruserok関数は /etc/hosts.equiv を先に調べて 該当しなければ .rhosts も次に調べます。第2引数に1を与えると、 iruserok関数は .rhosts だけを調べます。 これらの信頼リストにユーザー名が指定されていない場合は 第3引数と第4引数は同一ユーザー名でないとなりません。