この節では、RPC の下位レベルインタフェースを使用するさまざまな開発テクニックを説明します。この章で説明する項目を次に示します。
サーバー上の poll() - サーバー上で svc_run() が呼び出せない場合に、サーバーから直接ディスパッチャを呼び出す方法
ブロードキャスト RPC - ブロードキャストの使用方法
バッチ処理 - 一連の呼び出しをバッチ処理にして、パフォーマンスを向上させる方法
認証 - 今回のリリースで使用される認証方法
ポートモニタの使用 - ポートモニタ inetd と listener のインタフェース
複数のプログラムのバージョン - 複数のプログラムバージョンのサービス方法
この節で説明する内容は、(デフォルトの) シングルスレッドのモードで RPC を実行する場合にだけ適用されます。
RPC 要求をサービスしたり、その他のアクティビティを実行したりするプロセスでは、svc_run() を呼び出せない場合があります。他のアクティビティで定期的にデータ構造を更新する場合は、プロセスから svc_run() を呼び出す前に SIGALRM 信号をセットできます。そうすると、シグナルハンドラがデータ構造を処理してから svc_run() に制御を戻します。
プロセスから svc_run() をバイパスして直接ディスパッチャにアクセスするには、svc_getreqset() を呼び出します。待っているプログラムに結合したトランスポート端点のファイル記述子がわかれば、プロセスは自分で poll() を呼び出して、RPC ファイル記述子と自身の記述子の両方で要求を待つことができます。
例 4-20 には svc_run() を示します。svc_pollset
は、_rpc_select_to_poll() の呼び出しを通して svc_fdset() から派生した pollfd 構造体の配列です。この配列は、RPC ライブラリルーチンのどれかが呼び出されるたびに変わる可能性があります。そのたびに記述子がオープンされ、クローズされるからです。poll() がいくつかの RPC ファイル記述子への RPC 要求の到着を確認すると、svc_getreq_poll() が呼び出されます。
関数 _rpc_dtbsize() と _rpc_select_to_poll() は、SVID の一部ではありませんが、libnsl ライブラリで使用できます。Solaris 以外でも実行できるように、これらの関数を作成するために、関数の仕様を説明します。
int __rpc_select_to_poll(int fdmax, fd_set *fdset, struct pollfd *pollset) |
ビットフラグとして fd_set ポインタとチェックすべきビット数が指定されます。この関数内で、指定された pollfd 配列を RPC が使用するために初期化するようにします。RPC は、入力イベントだけをポーリングします。初期化された pollfd スロット数が返されます。
int __rpc_dtbsize() |
この関数は、getrlimit() 関数を呼び出し、新しく作成された記述子にシステムが割り当てる最大値を決定します。結果は、効率化のためにキャッシュされます。
この節の SVID ルーチンについての詳細は、rpc_svc_calls(3NSL) および poll(2) のマニュアルページを参照してください。
void svc_run() { int nfds; int dtbsize = __rpc_dtbsize(); int i; struct pollfd svc_pollset[fd_setsize]; for (;;) { /* * 要求待ちするサーバー fd があるかどうかをチェック */ nfds = __rpc_select_to_poll(dtbsize, &svc_fdset, svc_pollset); if (nfds == 0) break; /* 要求待ちの fd がないので終了 */ switch (i = poll(svc_pollset, nfds, -1)) { case -1: /* * エラーが起こった場合は、poll() ではなく、シグナルハンドラなど * 外部イベントによるものと考えて、無視して継続する */ case 0: continue; default: svc_getreq_poll(svc_pollset, i); } } } |
RPC のブロードキャストが要求されると、メッセージはネットワーク上の rpcbind デーモンに送られます。要求されたサービスが登録されている rpcbind デーモンは、その要求をサーバーに送ります。ブロードキャスト RPC と通常の RPC 呼び出しとの主な相違点を次に示します。
通常の RPC では応答は 1 つですが、ブロードキャスト RPC には複数の応答があります (メッセージに応答するすべてのマシンから応答が返されます)。
ブロードキャスト RPC は、UDP のようにブロードキャスト RPC をサポートする非接続型プロトコルでしか使用できません。
ブロードキャスト RPC では、正常終了以外の応答は返されません。したがって、ブロードキャスタと遠隔のサービスでバージョンの不一致があれば、サービスからブロードキャスタには何も返されません。
ブロードキャスト RPC では、rpcbind で登録されたデータグラムサービスだけがアクセス可能です。サービスアドレスはホストごとに異なるので、rpc_broadcast() は、rpcbind のネットワークアドレスにメッセージを送信します。
ブロードキャスト要求のサイズはローカルネットワークの最大伝送ユニット (MTU:maximum trasfer unit) により制限されます。Ethernet の MTU は 1500 バイトです。
例 4-21 では、rpc_broadcast() の使用方法を示し、引数を説明します。
/* * bcast.c: RPC ブロードキャストの使用例 */ #include <stdio.h> #include <rpc/rpc.h> main(argc, argv) int argc; char *argv[]; { enum clnt_stat rpc_stat; rpcprog_t prognum; rpcvers_t vers; struct rpcent *re; if(argc != 3) { fprintf(stderr, "usage : %s RPC_PROG VERSION¥n", argv[0]); exit(1); } if (isdigit( *argv[1])) prognum = atoi(argv[1]); else { re = getrpcbyname(argv[1]); if (! re) { fprintf(stderr, "Unknown RPC service %s¥n", argv[1]); exit(1); } prognum = re->r_number; } vers = atoi(argv[2]); rpc_stat = rpc_broadcast(prognum, vers, NULLPROC, xdr_void, (char *)NULL, xdr_void, (char *)NULL, bcast_proc, NULL); if ((rpc_stat != RPC_SUCCESS) && (rpc_stat != RPC_TIMEDOUT)) { fprintf(stderr, "broadcast failed: %s¥n", clnt_sperrno(rpc_stat)); exit(1); } exit(0); } |
例 4-22 の関数 bcast_proc() では、ブロードキャストに対する応答を収集します。通常は、最初の応答だけを取り出すか、応答をすべて収集します。bcast_proc() は、応答を返したサーバーの IP アドレスを表示します。この関数は FALSE を返して応答の収集を続け、RPC クライアントコードはタイムアウトになるまでブロードキャストを再送信し続けます。
bool_t bcast_proc(res, t_addr, nconf) void *res; /* 応答なし */ struct t_bind *t_addr; /* 応答したアドレス */ struct netconfig *nconf; { register struct hostent *hp; char *naddr; naddr = taddr2naddr(nconf, &taddr->addr); if (naddr == (char *) NULL) { fprintf(stderr,"Responded: unknown¥n"); } else { fprintf(stderr,"Responded: %s¥n", naddr); free(naddr); } return(FALSE); } |
TRUE が返されるとブロードキャストは終了し、rpc_broadcast() は正常終了します。FALSE が返された場合は、次の応答を待ちます。数秒間待ってから、要求が再びブロードキャストされます。応答が返されない場合は、rpc_broadcast() は RPC_TIMEDOUT を返します。
RPC の設計方針では、クライアントは呼び出しメッセージを送信して、サーバーがそれに応答するのを待ちます。すなわち、サーバーが要求を処理する間、クライアントは停止していることになります。これは、クライアントが各メッセージへの応答を待つ必要がないときには非効率です。
RPC のバッチ処理を使用すると、クライアントは非同期に処理を進めることができます。RPC メッセージは呼び出しパイプラインに入れてサーバーに送られます。バッチ処理では次のことが必要になります。
サーバーはどのような中間メッセージにも応答しない
呼び出しパイプラインは、信頼性の高いトランスポート (たとえば、TCP) で伝送される
呼び出し時に指定する、戻り値に対する XDR ルーチンは NULL である
RPC 呼び出しのタイムアウト値はゼロである
サーバーはそれぞれの呼び出しに対しては応答しないので、クライアントは、サーバーが前の呼び出しを処理している間に平行して次の呼び出しを送信できます。トランスポートは複数の呼び出しメッセージをバッファリングし、システムコール write() で一度にサーバーに送信します。このため、プロセス間通信のオーバヘッドが減少し、一連の呼び出しに要する総時間が短縮されます。クライアントは終了前に、パイプラインをフラッシュする呼び出しをバッチにしないで実行します。
例 4-23 には、バッチ処理を使用しないクライアント側プログラムを示します。文字配列 buf を走査して文字列を順に取り出し、1 つずつサーバーに送信します。
#include <stdio.h> #include <rpc/rpc.h> #include "windows.h" main(argc, argv) int argc; char **argv; { struct timeval total_timeout; register CLIENT *client; enum clnt_stat clnt_stat; char buf[1000], *s = buf; if ((client = clnt_create( argv[1], WINDOWPROG, WINDOWVERS, "circuit_v")) == (CLIENT *) NULL) { clnt_pcreateerror("clnt_create"); exit(1); } total_timeout.tv_sec = 20; total_timeout.tv_usec = 0; while (scanf( "%s", s ) != EOF) { if (clnt_call(client, RENDERSTRING, xdr_wrapstring, &s, xdr_void, (caddr_t) NULL, total_timeout) != RPC_SUCCESS) { clnt_perror(client, "rpc"); exit(1); } } clnt_destroy( client ); exit(0); } |
例 4-24 には、このクライアントプログラムでバッチ処理を使用する場合を示します。各文字列の送信には応答を待たず、サーバーからの終了応答だけを待ちます。
#include <stdio.h> #include <rpc/rpc.h> #include "windows.h" main(argc, argv) int argc; char **argv; { struct timeval total_timeout; register CLIENT *client; enum clnt_stat clnt_stat; char buf[1000], *s = buf; if ((client = clnt_create( argv[1], WINDOWPROG, WINDOWVERS, "circuit_v")) == (CLIENT *) NULL) { clnt_pcreateerror("clnt_create"); exit(1); } timerclear(&total_timeout); while (scanf("%s", s) != EOF) clnt_call(client, RENDERSTRING_BATCHED, xdr_wrapstring, &s, xdr_void, (caddr_t) NULL, total_timeout); /* ここでパイプラインをフラッシュ*/ total_timeout.tv_sec = 20; clnt_stat = clnt_call(client, NULLPROC, xdr_void, (caddr_t) NULL, xdr_void, (caddr_t) NULL, total_timeout); if (clnt_stat != RPC_SUCCESS) { clnt_perror(client, "rpc"); exit(1); } clnt_destroy(client); exit(0); } |
例 4-25 には、バッチ処理を使用した場合のサーバーのディスパッチ部分を示します。サーバーは、メッセージを送信しないので、クライアント側は、失敗に気付きません。
#include <stdio.h> #include <rpc/rpc.h> #include "windows.h" void windowdispatch(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { char *s = NULL; switch(rqstp->rq_proc) { case NULLPROC: if (!svc_sendreply( transp, xdr_void, NULL)) fprintf(stderr, "can't reply to RPC call¥n"); return; case RENDERSTRING: if (!svc_getargs( transp, xdr_wrapstring, &s)) { fprintf(stderr, "can't decode arguments¥n"); /* 呼び出し側にエラーを通知 */ svcerr_decode(transp); break; } /* 文字列 s を処理するコード */ if (!svc_sendreply( transp, xdr_void, (caddr_t) NULL)) fprintf( stderr, "can't reply to RPC call¥n"); break; case RENDERSTRING_BATCHED: if (!svc_getargs(transp, xdr_wrapstring, &s)) { fprintf(stderr, "can't decode arguments¥n"); /* プロトコルエラーのため何も返さない */ break; } /* 文字列 s を処理するコード。ただし応答はしない。 */ break; default: svcerr_noproc(transp); return; } /* 引数の復号化で割り当てた文字列を解放 */ svc_freeargs(transp, xdr_wrapstring, &s); } |
バッチ処理によるパフォーマンスの向上を調べるために、例 4-23、例 4-25で 25144 行のファイルを処理しました。このサービスは、ファイルの各行を引き渡すだけの簡単なサービスです。バッチ処理を使用した方が、使用しない場合の 4 倍の速さで終了しました。
この章でこれまでに示した例では、呼び出し側は自分自身の ID をサーバーに示さず、サーバーも呼び出し側の ID を要求しませんでした。ネットワークサービスによっては、ネットワークファイルシステムのように、呼び出し側の ID が要求される場合があります。『Solaris のシステム管理』を参照して、この節で説明したいずれかの認証の方法を実行してください。
RPC のクライアントとサーバーを作成するときにさまざまなトランスポートを指定できるように、RPC クライアントにもさまざまなタイプの認証メカニズムを採用できます。RPC の認証サブシステムは端点が開かれているので、認証はさまざな使用法がサポートされます。認証プロトコルは、付録 B 「RPC プロトコルおよび言語の仕様」 で詳細に定義されています。
RPC が現在サポートしている認証タイプを 表 4-7 に示します。
表 4-7 RPC が現在サポートしている認証タイプ
デフォルト。認証は実行されない |
|
UNIX オペレーティングシステムのプロセスアクセス権を基にした認証タイプ |
|
サーバーによっては効率向上のため AUTH_SYS の代わりに AUTH_SHORT を使用できる。AUTH_SYS 認証を使用するクライアントプログラムは、サーバーからの AUTH_SHORT 応答ベリファイアを受信できる。詳細は、付録 B 「RPC プロトコルおよび言語の仕様」 を参照 |
|
DES 暗号化技法を基にした認証タイプ |
|
呼び出し側が次の方法で RPC クライアントハンドルを新規作成する場合を考えます。
clnt = clnt_create(host, prognum, versnum, nettype); |
この場合対応するクライアント作成ルーチンが次のように認証ハンドルを設定します。
clnt->cl_auth = authnone_create(); |
新たな認証インスタンスを作成するときは、auth_destroy(clnt->cl_auth) を使用して現在のインスタンスを破棄します。この操作はメモリーの節約のために必要です。
サーバー側では、RPC パッケージがサービスディスパッチルーチンに、任意の認証スタイルが結合されている要求を渡します。サービスディスパッチルーチンに渡された要求ハンドルには、rq_cred という構造体が入っています。その構成は、認証資格タイプを示すフィールドを除いて、ユーザーから隠されています。
/* * 認証データ */ struct opaque_auth { enum_t oa_flavor; /* 資格スタイル */ caddr_t oa_base; /* より詳細な認証データのアドレス */ u_int oa_length; /* 最大 MAX_AUTH_BYTES まで */ }; |
RPC パッケージでは、サービスディスパッチルーチンに対して次のことを保証しています。
svc_req 構造内の rq_cred フィールドは完全に設定済みです。したがって、rq_cred.oa_flavor を調べて認証タイプを得ることができます。得られた認証タイプが RPC でサポートされていない場合は、rq_cred のその他のフィールドも調べることができます。
サービス手続きに引き渡される rq_clntcred フィールドには NULL が入っているか、サポートされている認証資格タイプの設定済み構造体へのポインタが入っています。AUTH_NONE タイプには認証データはありません。rq_clntcred は、authsys_parms、short_hand_verf、 authkerb_cred、authdes_cred の各構造体へのポインタにだけキャストできます。
クライアント側で AUTH_SYS (旧バージョンでは AUTH_UNIX) タイプの認証を使用するには、RPC クライアントハンドルの作成後に clnt->cl_auth を次のように設定します。
clnt->cl_auth = authsys_create_default(); |
以降は、この clnt を使用した RPC 呼び出しでは、clnt とともに例 4-26 に示す資格 - 認証構造体が渡されます。
/* * AUTH_SYS タイプの資格 */ struct authsys_parms { u_long aup_time; /* 資格作成時刻 */ char *aup_machname; /* クライアント側のホスト名 */ uid_t aup_uid; /* クライアント側の実効 uid */ gid_t aup_gid; /* クライアント側の現在のグループ ID */ u_int aup_len; /* aup_gids の配列の長さ */ gid_t *aup_gids; /* ユーザーが所属するグループの配列 */ }; |
rpc.broadcast では、デフォルトで AUTH_SYS タイプの認証になります。
例 4-27 には、手続きを使用し、ネットワーク上のユーザー数を返すサーバープログラムである RUSERPROC_1() を示します。認証の例として AUTH_SYS タイプの資格をチェックし、呼び出し側の uid が 16 の場合は要求に応じないようにしてあります。
nuser(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { struct authsys_parms *sys_cred; uid_t uid; unsigned int nusers; /* NULLPROC の場合は認証データなし */ if (rqstp->rq_proc == NULLPROC) { if (!svc_sendreply( transp, xdr_void, (caddr_t) NULL)) fprintf(stderr, "can't reply to RPC call¥n"); return; } /* ここで uid を取得 */ switch(rqstp->rq_cred.oa_flavor) { case AUTH_SYS: sys_cred = (struct authsys_parms *) rqstp->rq_clntcred; uid = sys_cred->aup_uid; break; default: svcerr_weakauth(transp); return; } switch(rqstp->rq_proc) { case RUSERSPROC_1: /* 呼び出し側が、この手続きの呼び出し資格を持っているかどうか確認 */ if (uid == 16) { svcerr_systemerr(transp); return; } /* * ユーザー数を求めて変数 nusers に設定するコード */ if (!svc_sendreply( transp, xdr_u_int, &nusers)) fprintf(stderr, "can't reply to RPC call¥n"); return; default: svcerr_noproc(transp); return; } } |
このプログラムでは次の点に注意してください。
NULLPROC (手続き番号はゼロ) に結合した認証パラメータは、通常はチェックされません。
サービスプロトコルでは、アクセスが拒否された場合のステータスを返さなければなりません。例 4-27のプロトコルでは、その代わりにサービスプリミティブ svcerr_systemerr() を呼び出しています。
最後の点で重要なのは、RPC の認証パッケージとサービスの関係です。RPC は認証を処理しますが、個々のサービスへのアクセス制御は行いません。サービス自体でアクセス制御の方針を決め、それがプロトコル内で戻り値として反映されるようにしなければなりません。
AUTH_SYS タイプより厳しいセキュリティレベルが要求されるプログラムでは、AUTH_DES タイプの認証を使用します。AUTH_SYS タイプは AUTH_DES タイプに簡単に変更できます。たとえば、authsys_create_default() を使用する代わりに、プログラムから authsys_create() を呼び出し、RPC 認証ハンドルを変更して目的のユーザー ID とホスト名を設定することができます。
AUTH_DES タイプの認証を使用するには、サーバー側とクライアント側の両方のホストで、keyserv() デーモンと NIS また NIS+ ネームサービスが実行されている必要があります。また、両方のホスト上のユーザーに対してネットワーク管理者が割り当てた公開鍵 / 秘密鍵ペアが、publickey() のデータベースに入っていなければなりません。ユーザーは keylogin() のコマンドを実行して自分の秘密鍵を暗号化しておく必要があります。通常は、ログインパスワードと Secure RPC パスワードが同一の場合には、これを login() で行います。
AUTH_DES タイプの認証を使用するには、クライアントが認証ハンドルを正しく設定しなければなりません。その例を次に示します。
cl->cl_auth = authdes_seccreate(servername, 60, server, (char *)NULL); |
最初の引数は、サーバープロセスのネットワーク名か、サーバープロセスの所有者のネット名です。サーバープロセスは通常 root プロセスで、次の関数呼び出しでネット名を得ることができます。
char servername[MAXNETNAMELEN]; host2netname(servername, server, (char *)NULL); |
servername は受信文字列へのポインタで、server はサーバープロセスが実行されているホスト名です。サーバープロセスがスーパーユーザー以外のユーザーから起動されている場合は、次のように user2netname() を呼び出します。
char servername[MAXNETNAMELEN]; user2netname(servername, serveruid(), (char *)NULL); |
serveruid() はサーバープロセスのユーザー id です。どちらの関数も最後の引数は、サーバーを含むドメイン名です。NULL を指定すると、ローカルドメイン名が使用されます。
authdes_seccreate() の第 2 引数では、このクライアントの資格の存在時間 (ウィンドウとも呼ばれる) を指定します。この例では 60 秒が指定されているので、この資格はクライアント側が RPC 呼び出しを行なってから、60 秒間で失われます。プログラムから再びこの資格を使用しようとしても、サーバー側の RPC サブシステムは、資格がすでに失われていることを知って、資格を失ったクライアントからの要求に答えません。また資格の存在時間中に別のプログラムがその資格を再使用しようとしても拒否されます。サーバー側の RPC サブシステムが最近作成された資格を保存していて、重複して使用できないようにするためです。
authdes_seccreate() の第 3 引数は、クロックを同期させる timehost 名です。AUTH_DES タイプの認証を使用するには、サーバーとクライアントの時間が一致していなければなりません。この例では、サーバーに同期させています。(char *)NULL
と指定すると同期しません。この指定は、クライアントとサーバーがすでに同期していることが確実な場合にだけ行なってください。
authdes_seccreate() の第 4 引数は、タイムスタンプとデータとを暗号化するための DES 暗号化キーへのポインタです。この例のように (char *)NULL
と指定した場合は、ランダムキーが選択されます。このキーは、認証ハンドルの ah_key フィールドに入っています。
サーバー側はクライアント側より簡単です。例 4-27 のサーバーを AUTH_DES タイプの認証を使用するように変更したものを、例 4-28 に示します。
#include <rpc/rpc.h> ... ... nuser(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { struct authdes_cred *des_cred; uid_t uid; gid_t gid; int gidlen; gid_t gidlist[10]; /* NULLPROC の場合は認証データなし */ if (rqstp->rq_proc == NULLPROC) { /* 元のプログラムと同じ */ } /* ここで uid を取得 */ switch(rqstp->rq_cred.oa_flavor) { case AUTH_DES: des_cred = (struct authdes_cred *) rqstp->rq_clntcred; if (! netname2user( des_cred->adc_fullname.name, &uid, &gid, &gidlen, gidlist)) { fprintf(stderr, "unknown user: %s¥n", des_cred->adc_fullname.name); svcerr_systemerr(transp); return; } break; default: svcerr_weakauth(transp); return; } /* 以降は元のプログラムと同じ */ |
netname2user() ルーチンは、ネットワーク名 (またはユーザーの netname) をローカルシステム ID に変換することに注意してください。このルーチンはグループ ID も返します (この例では使用していません)。
SunOS 5.x は、klogin 以外の Kerberos V5 の大部分のクライアント側機能をサポートします。AUTH_KERB は AUTH_DES と概念的に同じです。主要な違いは、DES がネットワーク名と暗号化された DES セッションキーを引き渡すのに対し、Kerberos は、暗号化されたサービスチケットを引き渡すことです。実装状態と相互運用性に影響を及ぼすその他の要因については、このあとで説明します。
詳細は、kerberos(3KRB) のマニュアルページと MIT Project Athena implementation of Kerberos の Steiner-Neuman-Shiller 報告書 [Steiner, Jennifer G., Neuman, Clifford, and Schiller, Jeffrey J. "Kerberos: An Authentication Service for Open Network Systems." USENIX Conference Proceedings, USENIX Association, カリフォルニア州, バークレー, June 1988.] を参照してください。MIT 文書には、athena-dist.mit.edu 上の FTP ディレクトリ、/pub/kerberos/doc または、ドキュメント URL、ftp://athena-dist.mit.edu/pub/kerberos/doc を使用して Mosaic でアクセスできます。
Kerberos はその資格が有効である時間ウィンドウの概念を使用します。クライアントまたはサーバーのクロックを制限しません。クライアントは、サーバーに指定されたウィンドウの時間を調整することによって、自身とサーバー間のずれを決定し、この違いを補う必要があります。具体的には、window を authkerb_seccreate() に引き数として渡します。この場合、ウィンドウは変わりません。timehost が authkerb_seccreate() の引き数として指定されると、クライアント側は timehost から時刻を取得して、時刻の差異によってタイムスタンプを変更します。時刻を同期化するには、さまざまな方法が使用できます。詳細は、kerberos_rpc(3KRB) のマニュアルページを参照してください。
Kerberos ユーザーは、一次名、インスタンス、領域によって識別されます。RPC 認証コードは、領域とインスタンスを無視しますが、Kerberos ライブラリコードは無視しません。ユーザー名は、クライアントとサーバー間で同じであると仮定します。これによって、サーバーは一次名をユーザー ID 情報に変換することができます。周知の名前として 2 つの書式が使用されます (領域は省略されます)。
root.host は、クライアント側 host の特権を与えられたユーザーを表します。
user.ignored は、ユーザー名が user であるユーザーを表します。インスタンは無視されます。
Kerberos は、完全資格名 (チケットとウィンドウを含むもの) の送信時に暗号文ブロックチェイン (CBC: Cipher Block Chaining) モード、それ以外の場合は、電子コードブック (ECB: Electronic Code Book) モードを使用します。CBC と ECB は、DES 暗号化のための 2 つの方法です。詳細は、des_crypt(3) のマニュアルページを参照してください。セッションキーは、CBC モードに対する初期入力ベクトルとして使用されます。表記は次のようになります。
xdr_type(object) |
これは、XDR が object を type
とみなして使用されることを示します。次のコードセクションの長さ (資格またはベリファイアのバイト数) を、4 バイト単位に丸めたサイズで表されます。完全資格名およびベリファイアは、次のようになります。
xdr_long(timestamp.seconds) xdr_long(timestamp.useconds) xdr_long(window) xdr_long(window - 1) |
セッションキーに等しい入力ベクトルを持つ CBC で暗号化を行うと、出力結果は次のような 2 つの DES 暗号化ブロックになります。
CB0 CB1.low CB1.high |
xdr_long(AUTH_KERB) xdr_long(length) xdr_enum(AKN_FULLNAME) xdr_bytes(ticket) xdr_opaque(CB1.high) |
xdr_long(AUTH_KERB) xdr_long(length) xdr_opaque(CB0) xdr_opaque(CB1.low) |
xdr_long(timestamp.seconds) xdr_long(timestamp.useconds) |
ニックネームは、ECB によって暗号化され、ECB0 と資格を得ます。
xdr_long(AUTH_KERB) xdr_long(length) xdr_enum(AKN_NICKNAME) xdr_opaque(akc_nickname) |
xdr_long(AUTH_KERB) xdr_long(length) xdr_opaque(ECB0) xdr_opaque(0) |
上述の認証タイプ (AUTH_SYS、AUTH_DES、AUTH_KERB) は、1 つの決まった見方で同じように扱うことができます。このため、新しいネットワーキング階層、Generic Security Standard API (汎用セキュリティ規格 API)、すなわち GSS-API が追加されています。GSS-API のフレームワークでは、認証に加え次の 2 つの「サービス」が提供されています。
「完全性」
一貫性サービスでは、GSS-API は下位層のメカニズムを使用してプログラム間で交換されるメッセージを認証します。暗号化チェックサムによって、以下が確立されます。
データの発信側から受信側への識別情報 (ID)
受信側から発信側への識別情報 (ID) (相互の認証が要求された場合)
伝送されたデータそのものの認証
「プライバシ」
プライバシサービスには、完全性サービスが含まれています。これに加えて、伝送データも「暗号化」され傍受者から保護されます。
米国の輸出の規約により、プライバシサービスはすべてのユーザーが利用できるわけではありません。
現在、GSS-API はまだ発表されていません。ただし、特定の GSS-API 機能は RPCSEC_GSS の機能 (この機能は「不透明な」(opaque 型) で扱うことができる) を通じて参照できます。プログラマはこれらの値に直接かかわる必要はありません。
RPCSEC_GSS API セキュリティタイプを使用すると、ONC RPC アプリケーションは、GSS-API の機能を利用することができます。RPCSEC_GSS は、次の図のように、GSS-API 階層の「最上部」に位置しています。
RPCSEC-GSS のプログラミングインタフェースを使用する場合は、ONC RPC アプリケーションは以下の項目を指定できます。
セキュリティのパラダイム。各種セキュリティメカニズムでは、1 つまたは複数レベルのデータ保護と同時に、それぞれ異なる種類のデータ保護を提供します。この場合、GSS-API によってサポートされる任意のセキュリティメカニズムを指定します (Kerberos v5、RSA 公開鍵など)。
プライバシまたは完全性のいずれかを指定します (あるいはどちらも指定しない)。デフォルトは完全性です。この項目はメカニズムに依存しません。
保護の質。QOP により、プライバシまたは完全性サービスを実現するために使用する暗号化アルゴリズムのタイプが指定されます。各セキュリティメカニズムには、それに関連する 1 つまたは複数の QOP があります。
アプリケーションは、RPCSEC_GSS によって提供される関数により、QOP およびメカニズムのリストを入手できます (「その他の関数」を参照)。開発者は、メカニズムと QOP をハードコード化し、使用するアプリケーション内に埋め込むことは避けてください。そうすれば、新しい、または異なるメカニズムおよび QOP を使用するためにアプリケーションを修正する必要はありません。
これまでは、「セキュリティタイプ」と「認証タイプ」は同じものを表していました。RPCSEC_GSS の導入によって、「タイプ」は現在、多少異なる意味を持ちます。タイプには、認証とともにサービス (一貫性またはプライバシ) を含むことができますが、現在は RPCSEC_GSS が、これを実行できる唯一のタイプです。
RPCSEC_GSS を使用すると、ONC RPC アプリケーションは、他のタイプを使用して行う場合と同様に、ピアにセキュリティコンテキストを確立し、データを交換してこのコンテキストを破棄します。一度コンテキストが確立されると、アプリケーションは、送信したデータユニットごとに QOP およびサービスを変更できます。
RPCSEC_GSS データタイプを含む RPCSEC_GSS の詳細については、rpcsec_gss(3NSL) のマニュアルページを参照してください。
表 4-8 は、RPCSEC_GSS コマンドを要約したものです。この表では、各関数の個別の説明ではなく、RPCSEC_GSS 関数の全般的な概要を示しています。各関数の詳細については、該当するマニュアルページを参照するか、RPCSEC_GSS データ構造のリストなどの概要が記載された、rpcsec_gss(3NSL) のマニュアルページを参照してください。
表 4-8 RPCSEC_GSS Functions処理 | 関数 | 入力 | 出力 |
---|---|---|---|
セキュリティコンテキストの作成 | rpc_gss_seccreate() | クライアントのハンドル、主体名、メカニズム、QOP、サービスタイプ | AUTH ハンドル |
コンテキストの QOP とサービスタイプの変更 | rpc_gss_set_defaults() | 古い QOP とサービス | 新しい QOP とサービス |
セキュリティの変換前に、データの最大サイズを示す | rpc_gss_max_data_length() | 伝送できる最大データサイズ | 変換前の最大データサイズ |
セキュリティの変換前に、データの最大サイズを示す | rpc_gss_svc_max_data_length() | 伝送できる最大データサイズ | 変換前の最大データサイズ |
表示するサーバーの主体名を設定する | rpc_gss_set_svc_name() | 主体名 , RPC プログラム、バージョン番号 | 正常に完了した場合は TRUE |
呼び出し元 (クライアント) の資格を得る | rpc_gss_getcred() | svc_req 構造へのポインタ | UNIX 資格、RPCSEC_GSS 資格、cookie |
(ユーザーの作成した) コールバック関数を指定する | rpc_gss_set_callback() | コールバック関数へのポインタ | 正常に完了した場合は TRUE |
固有のパラメータから主体名の RPCSEC_GSS 構造を作成する | rpc_gss_get_principal_name() | メカニズム、ユーザー名、マシン名、ドメイン名 | RPCSEC_GSS 主体名の構造 |
RPCSEC_GSS ルーチンが失敗した場合にエラーコードを得る | rpc_gss_get_error() |
| RPCSEC_GSS エラー番号、該当する場合には errno |
インストールされているメカニズムの文字列を入手する | rpc_gss_get_mechanisms() |
| 有効なメカニズムのリスト |
有効な QOP 文字列を入手する | rpc_gss_get_mech_info() | メカニズム | そのメカニズムの有効な QOP |
サポートされている RPCSEC_GSS の最大および最小のバージョン番号を得る | rpc_gss_get_versions() |
| 最大および最小のバージョン番号 |
メカニズムが導入されているかどうかをチェックする | rpc_gss_is_installed() | メカニズム | インストールされている場合は TRUE |
ASCII メカニズムを RPC オブジェクト識別子に変換する | rpc_gss_mech_to_oid() | メカニズム (文字列で) | メカニズム (OID で) |
ASCII QOP を整数に変換する | rpc_gss_qop_to_num() | QOP (文字列で) | QOP (整数で) |
コンテキストは、rpc_gss_seccreate() 呼び出しを使用して作成します。この関数では引数として次のものをとります。
クライアントハンドル(たとえば、clnt_create()によって戻されたもの)
サーバーの主体名 ( nfs@acme.com)
セッションのメカニズム (Kerberos V5 など)
セキュリティサービスタイプ (プライバシなど)
セッションの QOP
使用される場合は、ほとんど不透明( opaque )なまま使用される (つまり、プログラマが NULL 値を入れることができる) 2 つの GSS-API パラメータ
この関数で、AUTH 認証ハンドルを返します。例 4-29 は、Kerberos v5 セキュリティメカニズムと完全性サービスを使用したコンテキストを作成する場合、rpc_gss_seccreate() がどのように使用されるかを示しています。
CLIENT *clnt; /* クライアントハンドル */ char server_host[] = "foo"; char service_name[] = "nfs@machine.eng.company.com"; char mech[] = "kerberosv5"; clnt = clnt_create(server_host, SERVER_PROG, SERV_VERS, "netpath"); clnt->clnt_auth = rpc_gss_seccreate(clnt, service_name, mech, ¥ rpc_gss_svc_integrity, NULL, NULL, NULL); . . . |
この例 4-29 では、次の点に注意してください。
メカニズムは明示的に宣言してありますが (読みやすくするために)、通常は、rpc_gss_get_mechanisms() 用いて、使用できるメカニズムの表から入手します。
QOP は、NULL として渡されます。これにより、QOP はこのメカニズムのデフォルトに設定されます。これ以外の場合は、このメカニズムを使用して、rpc_gss_get_mechanisms() を指定したプログラムで有効な値を入手できます。詳細については、マニュアルページの rpc_gss_get_mechanisms(3NSL) を参照してください。
セキュリティサービスタイプ、rpc_gss_svc_integrity は、RPCSEC_GSS タイプの enum のひとつである rpc_gss_service_t です。rpc_gss_service_t のフォーマットは、次のようになります。
typedef enum { rpc_gss_svc_default = 0, rpc_gss_svc_none = 1, rpc_gss_svc_integrity = 2, rpc_gss_svc_privacy = 3 } rpc_gss_service_t; |
デフォルトのセキュリティサービスは、完全性をマップするため、プログラマは指定された rpc_gss_svc_default を入手し、同じ結果を獲得することができます。
詳細については、rpc_gss_seccreate(3NSL) のマニュアルページを参照してください。
コンテキストが設定されると、アプリケーションは伝送される個々のデータユニットの QOP およびサービス値を変更する必要がある場合があります。たとえば、プログラムのパスワードは暗号化したいがログイン名は暗号化したくない場合。これは、次のように rpc_gss_set_defaults() を使用すると実行できます。
rpc_gss_set_defaults(clnt->clnt_auth, rpc_gss_svc_privacy, qop); . . . |
この場合、セキュリティサービスはプライバシに設定されます (「コンテキストの作成」を参照)。
ここで、qop は新しいQOPの名前を表わす文字列へのポインタです。
コンテキストは、通常どおり、auth_destroy() を使用して破棄します。
QOP とサービスの変更に関する詳細は、rpc_gss_set_defaults(3NSL) のマニュアルページを参照してください。
セキュリティコンテキストを確立し、保持するには、次の 2 つのタイプの主体名が必要です。
サーバーの主体名は、通常、「service@host」の形式の NULL で終わる ASCII 文字列で指定します。たとえば、 nfs@eng.acme.com のように指定します。
クライアントが rpc_gss_seccreate() でセキュリティコンテキストを作成する時に、このフォーマットでサーバーの主体名を指定します (「コンテキストの作成」を参照)。同様にサーバーは、表示する主体名を設定する必要がある場合は、引数としてこのフォーマットの主体名をとる rpc_gss_set_svc_name() を使用します。
サーバーが受信するクライアントの主体名は、rpc_gss_principal_t 構造の形式 (使用するメカニズムによって決定される、不透明に長さを暗示したバイト列) をとります。この構造については、rpcsec_gss(3NSL) のマニュアルページを参照してください 。
サーバーは、起動時に、そのサーバーを表わす主体名を指定する必要があります (1 つのサーバーが複数の主体として機能する場合もあります)。サーバー主体名の設定には、rpc_gss_set_svc_name() を使用します。
char *principal, *mechanism; u_int req_time; principal = "nfs@eng.acme.com"; mechanism = "kerberos_v5"; req_time = 10000; /* 資格の有効時間 */ rpc_gss_set_svc_name(principal, mechanism, req_time, SERV_PROG, SERV_VERS);
Kerberos は、req_time パラメータを無視します。他の認証システムでは、このパラメータを使用する場合があります。
詳細については、rpc_gss_set_svc_name(3NSL) のマニュアルページを参照してください。
サーバーは、クライアントの主体名で稼動できなければなりません。たとえば、クライアントの主体名をアクセス制御リストと比較するため、またはクライアントの UNIX 資格を検出するため (このような資格が存在する場合) に必要です。このような主体名は、rpc_gss_principal_t 構造ポインタのフォームで維持されます (rpc_gss_principal_t の詳細については、rpcsec_gss(3NSL) のマニュアルページを参照してください)。サーバーが、受信した主体名を既知のエンティティの名前と比較する必要がある場合、サーバーは、この形式で rpc_gss_principal_t 主体名を生成できなければなりません。
rpc_gss_get_principal_name() 呼び出しでは、ネットワーク上で個人を識別するパラメータをいくつか入力し、rpc_gss_principal_t 構造ポインタとして主体名を生成します。
rpc_gss_principal_t *principal; rpc_gss_get_principal_name(principal, mechanism, name, node, domain); . . .
rpc_gss_get_principal_name() への引数は、次のとおりです。
「 principal 」には、設定された rpc_gss_principal_t 構造へのポインタが入ります。
「 mechanism 」には、使用されるセキュリティメカニズムです (生成される主体名は、メカニズムに依存することに注意してください)。
「 name 」には joeh または nfs などの個人名、またはサービス名が入ります。
「 node 」には、UNIX マシン名などが入ります。
「 domain 」には、たとえば、DNS、NIS、または NIS+ドメイン名、あるいは Kerberos の領域が入ります。
各セキュリティメカニズムには、別々の識別パラメータが必要です。たとえば、Kerberos V5 にはユーザー名が必ず必要です。また、オプションの場合に限り、修飾されたノード名とドメイン名が必要です (Kerberos 用語では、ホスト名と領域名)。
詳細については、rpc_gss_get_principal_name(3NSL) のマニュアルページを参照してください。
主体名は、free() ライブラリコールを使用して解放します。
サーバーは、クライアントの資格を獲得できなければなりません。 例 4-33 で示すように、rpc_gss_getcred() 関数を使用すると、サーバーは UNIX 資格または RPCSEC_GSS 資格のいずれか (またはこの両方) を検索できます。これは、この関数が正常に終了した場合に設定された 2 つの引数によって実行されます。このうち1つは、呼び出し元の UNIX 資格が組み込まれた rpc_gss_ucred_t 構造 (存在する場合) へのポインタになります。
typedef struct { uid_t uid; /* ユーザー ID */ gid_t gid; /* グループ ID */ short gidlen; git_t *gidlist; /* グループのリスト */ } rpc_gss_ucred_t; |
もう 1 つの引数は、次のような、rpc_gss_raw_cred_t 構造へのポインタです。
typedef struct { u_int version; /* RPCSEC_GSS プログラムバージョン */ char *mechanism; char *qop; rpc_gss_principal_t *client_principal; /* クライアント主体名 */ char *svc_principal; /* サーバー主体名 */ rpc_gss_service_t service; /* プライバシ、完全性 enum */ } rpc_gss_rawcred_t; |
例 4-33 は 1 つのサーバー側のディスパッチ手続きの例です。これにより、サーバーは呼び出し元の資格を入手します。この手続きでは、呼び出し元の UNIX 資格を入手してから、次に rpc_gss_rcred_t 引数内で検出された、メカニズム、QOP、サービスタイプを使用してユーザーの識別情報 (ID) を確認します。
static void server_prog(struct svc_req *rqstp, SVCXPRT *xprt) { rpc_gss_ucred_t *ucred; rpc_gss_rawcred_t *rcred; if (rqst->rq_proq == NULLPROC) { svc_sendreply(xprt, xdr_void, NULL); return; } /* * 他の全ての要求を認証する */ */ switch (rqstp->rq_cred.oa_flavor) { case RPCSEC_GSS: /* * 資格情報を取得する */ rpc_gss_getcred(rqstp, &rcred, &ucred, NULL); /* * 設定ファイルを参照してセキュリティパラメータを * 使用することでユーザーにアクセスが許可されている * ことを確認する */ if (!authenticate_user(ucred->uid, rcred->mechanism, rcred->qop, rcred->service)) { svcerr_weakauth(xprt); return; } break; /* ユーザーに許可する */ default: svcerr_weakauth(xprt); return; } /* スイッチの終り */ switch (rqstp->rq_proq) { case SERV_PROC1: . . . } /* 通常の要求処理 ; 応答を送る ... */ return; } |
詳細については、rpc_gss_getcred(3NSL) のマニュアルページを参照してください。
例 4-33 では、 rpc_gss_getcred()への最後の引数は、ユーザー定義の cookie です。このコンテキストの作成時にサーバーによってどのような値が指定されていても、このユーザー定義の値が戻されます。この cookie は 4 バイトの値で、そのアプリケーションに適したあらゆる方法で使用されます。RPC はこれを解釈しません。たとえば、 cookie は、コンテキストの起動元を示す構造へのポインタまたはインデックスになることができます。また、各要求ごとにこの値を計算する代わりに、サーバーがコンテキスト作成時にこの値を計算します。このため、要求の処理時間が削減されます。
これ以外に cookie が使用される場所は、コールバックです。サーバーは、rpc_gss_set_callback() 関数を使用することにより、(ユーザー定義の) コールバックを指定してコンテキストが最初に使用された時を認知できます。コールバックは、コンテキストが指定されたプログラムとバージョン用に確立されたあとに、そのコンテキストがデータ交換に最初に使用された時に呼び出されます。
ユーザー定義のコールバックルーチンは、以下のような形式になります。
2 番めと 3 番めの引数 deleg と gss_context は、GSS-API データタイプで、現在はまだ公開されていません。そのため、コールバック関数はこれらを無視します。簡単に説明すると、プログラムが GSS-API オペレーションをこのコンテキスト上で実行する必要がある場合、すなわち受信条件のテストをする場合、deleg は代表されるピアの識別情報になり、一方 gss_context は GSS-API コンテキストへのポインタになります。cookie 引数については、すでに説明しました。
lock 引数は、以下のように rpc_gss_lock_t 構造へのポインタです。
typedef struct { bool_t locked; rpc_gss_rawcred_t *raw_cred; } rpc_gss_lock_t; |
詳細は、 rpc_gss_set_callback(3NSL) のマニュアルページを参照してください。
rpc_gss_max_data_length() と rpc_gss_svc_max_data_length() の 2 つの関数は、1 つのデータが、セキュリティ測度によって変換され「ワイヤを通じて」送信される前に、そのデータの大きさを判別する場合に便利です。つまり、暗号化などのセキュリティ変換により、通常、伝送される 1 つのデータのサイズは変更されます (通常は、大きくなる)。データが使用できるサイズ以上に大きくならないように、これら 2 つの関数 (前者はクライアント側バージョンで、後者はサーバー側バージョン) により、指定されたトランスポートの変換前の最大サイズが戻されます。
詳細については、rpc_gss_max_data_length(3NSL) と rpc_gss_svc_max_data_length(3NSL) のマニュアルページを参照してください。
関数の中には、導入されたセキュリティシステムに関する情報を入手する場合に使用できるものもあります。
rpc_gss_get_mechanisms() は、導入されたセキュリティメカニズムのリストを戻します。
rpc_gss_is_installed() は、指定したメカニズムがインストールされているかどうかを検査します。
rpc_gss_get_mech_info() は、指定されたメカニズムの有効な QOP を戻します。
これらの関数を使用することによって、プログラマは、アプリケーション内のセキュリティパラメータのハードコード化を避けることができます (RPCSEC_GSS 関数については、表 4-8 と rpcsec_gss(3NSL) のマニュアルページを参照)。
RPCSEC_GSS は各種のファイルを使用して情報を保存します。
サーバーが要求に関連するクライアントの資格を検索すると、サーバーはクライアントの主体名 (rpc_gss_principal_t 構造ポインタの形式)、またはクライアントのローカル UNIX 資格 (UID) のいずれかを入手できます。NFS 要求などのサービス では、アクセス検査に必要なローカル UNIX 資格が必要ですが、他の資格は必要ありません。つまり、これらのサービスでは、たとえば主体名は、rpc_gss_principal_t 構造として直接、独自のアクセス制御リスト内に格納できるからです。
クライアントのネットワーク資格 (その主体名) とローカル UNIX 資格間の対応は自動的に行われません。これは、ローカルのセキュリティ管理者が明示的に設定する必要があります。
gsscred ファイルには、クライアントの UNIX 資格とネットワーク(たとえば、Kerberos V5) 資格の両方が入っています。後者は、rpc_gss_principal_t 構造の Hex-ASCII 表示です。これには、XFN を通じてアクセスするため、このテーブルは、ファイル、NIS、NIS+、あるいは XFN によってサポートされる将来のネームサービス上に導入できます。XFN 階層では、このテーブルは this_org_unit/service/gsscred として表示されます。 gsscred テーブルは、gsscred ユーティリティとともに保持されます。このユーティリティを使用すると、管理者はユーザーやメカニズムの追加および削除が行えます。
便宜上、RPCSEC_GSS では、メカニズムと保護の質 (QOP) パラメータを表示するためにリテラルの文字列を使用します。ただし、基本的なメカニズム自体では、メカニズムをオブジェクト識別子として、QOP は 32 ビット整数として表示する必要があります。また、各メカニズムごとに、そのメカニズムのサービスを実現する共有ライブラリを指定する必要があります。
/etc/gss/mech ファイルには、システム上に導入されたすべてのメカニズムに関する情報、メカニズム名 (ASCII 形式)、メカニズムの ODI、このメカニズムによって使用できるサービスを実現する共有ライブラリ、サービスを実現するカーネルモジュールが格納されます。次に例を示します。
kerberos_v5 1.2.840.113554.1.2.2 gl/mech_krb5.so gl_kmech_krb5 |
/etc/gss/qop ファイルには、導入されたすべてのメカニズム用に、各メカニズムがサポートするすべての QOP が、ASCII 文字列とそれに対応する 32 ビット整数の両方で格納されます。
/etc/gss/mech と /etc/gss/qop は、両方とも指定されたシステムにセキュリティメカニズムが最初に導入されたときに作成されます。
カーネル内 RPC ルーチンは、通常、文字列にない値を使用してメカニズムと QOP を表すため、アプリケーションは、これらのカーネル内ルーチンを利用したい場合には、rpc_gss_mech_to_oid() と rpc_gss_qop_to_num() 関数を使用してこれらのパラメータと同等の文字列にない値を入手します。
RPC サーバーは、inetd や listen のようなポートモニタから起動できます。ポートモニタは、要求が来ているかどうか監視し、要求が来ればそれに応じてサーバーを生成します。生成されたサーバープロセスには、要求を受信したファイル記述子 0 が渡されます。inetd の場合、サーバーは処理を終えるとすぐに終了するか、次の要求がくる場合に備えて指定された時間だけ待ってから終了します。
listen の場合は常に新たなプロセスが生成されるため、サーバーは応答を返したらすぐに終了しなければなりません。次に示す関数呼び出しでは、ポートモニタから起動されるサービスで使用する SVCXPRT ハンドルが作成されます。
transp = svc_tli_create(0, nconf, (struct t_bind *)NULL, 0, 0) |
ここで、nconf は要求を受信したトランスポートの netconfig 構造体です。
サービスはポートモニタによりすでに rpcbind で登録されているので、登録する必要はありません。ただし、サービス手続きは次のように svc_reg() を呼び出して登録しなければなりません。
svc_reg(transp, PROGNUM, VERSNUM, dispatch,(struct netconfig *)NULL) |
ここでは netconfig 構造体として NULL を渡し、svc_reg() が rpcbind を呼び出してサービスを登録しないようにしています。
rpcgen が生成したサーバー側スタブプログラムを調べて、これらのルーチンの呼び出し順序を確認してください。
接続型トランスポートの場合は、次のルーチンにより下位レベルインタフェースが提供されます。
transp = svc_fd_create(0, recvsize, sendsize); |
最初の引数ではファイル記述子 0 を指定します。recvsize と sendsize には、適当なバッファサイズを指定できます。どちらの引数も 0 とすると、システムのデフォルト値が使用されます。自分で監視を行わないアプリケーションサーバーの場合は、svc_fd_create() を使用します。
/etc/inet/inetd.conf のエントリ形式は、ソケットサービス、TLI サービス、RPC サービスによってそれぞれ異なります。RPC サービスの場合の inetd.conf のエントリ形式は次のようになります。
rpc_prog/vers endpoint_type rpc/proto flags user pathname args |
各エントリの内容を次に示します。
表 4-9 RPC inetd サービス
rpc_prog/vers |
RPC プログラム名に / とバージョン番号 (またはバージョン番号の範囲) を付けたもの |
endpoint_type |
dgram (非接続型ソケット)、stream (接続型ソケット)、tli (TLI 端点) のどれか |
proto |
サポートされているトランスポートすべてを意味する *、nettype、netid のどれか。または、nettype と netid をコンマで区切ったリスト |
flags |
wait または nowait のどちらか |
user |
有効な passwd データベースに存在しているユーザー |
pathname |
サーバーデーモンへのフルパス名 |
args |
デーモンの呼び出し時に渡される引数 |
エントリの例を次に示します。
rquotad/1 tli rpc/udp wait root /usr/lib/nfs/rquotad rquotad |
inetd についての詳細は、inetd.conf(4) のマニュアルページを参照してください。
次に示すように pmadm を使用して RPC サービスを追加します。
pmadm -a -p pm_tag -s svctag -i id -v vers ¥ -m `nlsadmin -c command -D -R prog:vers` |
引数の -a はサービスの追加を意味します。-p pm_tag ではサービスへのアクセスを提供するポートモニタに結合したタグを指定します。-s svctag はサーバーの ID コードです。-i id はサービス svctag に割り当てられた /etc/passwd 内のユーザー名です。-v vers はポートモニタのデータベースファイルのバージョン番号です。-m ではサービスを呼び出す nlsadmin コマンドを指定します。nlsadmin コマンドには引数を渡すことができます。たとえば、rusersd という名前の遠隔プログラムサーバーのバージョン 1 を追加する場合は、pmadm コマンドは次のようになります。
# pmadm -a -p tcp -s rusers -i root -v 4 ¥ -m `nlsadmin -c /usr/sbin/rpc.ruserd -D -R 100002:1` |
このコマンドでは、root パーミッションが指定され、listener データベースファイルのバージョン 4 でインストールされ、TCP トランスポート上で使用可能になります。pmadm の引数やオプションは複雑であるため、RPC サービスはコマンドスクリプトでもメニューシステムでも追加できます。メニューシステムを使用するには、sysadm ports と入力して、port_services オプションを選択します。
サービスを追加した場合は、その後リスナを再初期化してサービスを利用可能にしなければなりません。そのためには、次のようにリスナを一度止めてから再起動します (このとき rpcbind が実行されていなければならないことに注意してください)。
# sacadm -k -p pmtag # sacadm -s -p pmtag |
リスナプロセスの設定などについての詳細は、listen(1M)、pmadm(1M)、sacadm(1M)、sysadm(1M) のマニュアルページと『Solaris のシステム管理 (第 3 巻)』を参照してください。
一般に、プログラム PROG の最初のバージョンは PROGVERS_ORIG とし、最新バージョンは PROGVERS と命名します。プログラムのバージョン番号は続き番号で割り当てなければなりません。バージョン番号に飛ばされた番号があると、検索したときに定義済みのバージョン番号を探し出せないようなことが起こります。
プログラムのバージョン番号は、プログラムの所有者以外は決して変更しないでください。自分が所有していないプログラムのバージョン番号を追加したりすると、そのプログラムの所有者がバージョン番号を追加するときに重大な問題が起こります。バージョン番号の登録やご質問はご購入先へお問い合わせ下さい。
ruser プログラムの新バージョンが、int
ではなく unsigned
short
を返すように変更されたとします。新バージョンの名前を RUSERSVERS_SHORT とすると、新旧の 2 つのバージョンをサポートするサーバーは二重登録することになります。次のように、どちらの登録でも同じサーバーハンドルを使用します。
if (!svc_reg(transp, RUSERSPROG, RUSERSVERS_ORIG, nuser, nconf)) { fprintf(stderr, "can't register RUSER service¥n"); exit(1); } if (!svc_reg(transp, RUSERSPROG, RUSERSVERS_SHORT, nuser, nconf)) { fprintf(stderr, "can't register RUSER service¥n"); exit(1); } |
次のように、1 つの手続きで両バージョンを実行できます。
void nuser(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { unsigned int nusers; unsigned short nusers2; switch(rqstp->rq_proc) { case NULLPROC: if (!svc_sendreply( transp, xdr_void, 0)) fprintf(stderr, "can't reply to RPC call¥n"); return; case RUSERSPROC_NUM: /* * ユーザー数を求めて変数 nusers に設定するコード */ switch(rqstp->rq_vers) { case RUSERSVERS_ORIG: if (! svc_sendreply( transp, xdr_u_int, &nusers)) fprintf(stderr, "can't reply to RPC call¥n"); break; case RUSERSVERS_SHORT: nusers2 = nusers; if (! svc_sendreply( transp, xdr_u_short, &nusers2)) fprintf(stderr, "can't reply to RPC call¥n"); break; } default: svcerr_noproc(transp); return; } return; } |
異なるホストでは RPC サーバーの異なるバージョンが実行されている可能性があるので、クライアントはさまざまなバージョンに対応できるようにしなければなりません。たとえば、あるサーバーでは旧バージョン RUSERSPROG(RUSERSVERS_ORIG) が実行されており、別のサーバーでは最新バージョン RUSERSPROG(RUSERSVERS_SHORT) が実行されているとします。
サーバーのバージョンがクライアント作成ルーチン clnt_call() で指定したバージョン番号と一致しない場合は、clnt_call() から RPCPROGVERSMISMATCH というエラーが返されます。サーバーがサポートしているバージョン番号を取り出して、正しいバージョン番号をもつクライアントハンドルを作成することもできます。そのためには、例 4-36 のルーチンを使用するか、clnt_create_vers() を使用します。詳細については、rpc(3NSL) のマニュアルページを参照してください。
main() { enum clnt_stat status; u_short num_s; u_int num_l; struct rpc_err rpcerr; int maxvers, minvers; CLIENT *clnt; clnt = clnt_create("remote", RUSERSPROG, RUSERSVERS_SHORT, "datagram_v"); if (clnt == (CLIENT *) NULL) { clnt_pcreateerror("unable to create client handle"); exit(1); } to.tv_sec = 10; /* タイムアウト値を設定 */ to.tv_usec = 0; status = clnt_call(clnt, RUSERSPROC_NUM, xdr_void, (caddr_t) NULL, xdr_u_short, (caddr_t)&num_s, to); if (status == RPC_SUCCESS) { /* 最新バージョン番号が見つかった場合 */ printf("num = %d¥n", num_s); exit(0); } if (status != RPC_PROGVERSMISMATCH) { /* その他のエラー */ clnt_perror(clnt, "rusers"); exit(1); } /* 指定したバージョンがサポートされていない場合 */ clnt_geterr(clnt, &rpcerr); maxvers = rpcerr.re_vers.high; /* サポートされている最新バージョン */ minvers = rpcerr.re_vers.low; /* サポートされている最も古いバージョン */ if (RUSERSVERS_SHORT < minvers || RUSERSVERS_SHORT > maxvers) { /* サポート範囲内にない場合 */ clnt_perror(clnt, "version mismatch"); exit(1); } (void) clnt_control(clnt, CLSET_VERSION, RUSERSVERS_ORIG); status = clnt_call(clnt, RUSERSPROC_NUM, xdr_void, (caddr_t) NULL, xdr_u_int, (caddr_t)&num_l, to); if (status == RPC_SUCCESS) /* 識別できるバージョン番号が見つかった場合 */ printf("num = %d¥n", num_l); else { clnt_perror(clnt, "rusers"); exit(1); } } |
場合によっては、動的に生成される RPC プログラム番号をアプリケーションが使用すると便利なことがあります。たとえば、コールバック手続きを実装する場合などです。コールバックでは、クライアントプログラムは通常、動的に生成される、つまり一時的な RPC プログラム番号を使用して RPC サービスを登録し、これを要求とともにサーバーに渡します。次にサーバーは一時的な RPC プログラム番号を使用してクライアントプログラムをコールバックし、結果を返します。クライアントの要求を処理するのにかなりの時間がかかり、クライアントが停止できない (シングルスレッドであると仮定して) 場合などには、この機構が必要になります。このような場合、サーバーはクライアントの要求を認識し、あとで結果とともにコールバックを行います。コールバックを使用する別の例としては、サーバーから定期的なレポートを生成する場合があります。クライアントは RPC 呼び出しを行い、報告を開始します。そしてサーバーはクライアントプログラムが提供する一時的な RPC プログラム番号を使用して、定期的にレポートとともにクライアントをコールバックします。
動的に生成される一時的な RPC 番号は、0x40000000 から 0x5fffffff の範囲です。次に示すルーチンは指定されるトランスポートタイプ用に、一時的な RPC プログラムに基づいてサービスを作成します。サービスハンドルと一時的な rpc プログラム番号が返されます。呼び出し側はサービスディスパッチルーチン、バージョンタイプ、トランスポートタイプを提供します。
SVCXPRT * register_transient_prog(dispatch, program, version, netid) void (*dispatch)(); /* サービスディスパッチルーチン */ rpcproc_t *program; /* 一時的な RPC 番号が返される */ rpcvers_t version; /* プログラムバージョン */ char *netid; /* トランスポート id */ { SVCXPRT *transp; struct netconfig *nconf; rpcprog_t prognum; if ((nconf = getnetconfigent(netid)) == (struct netconfig *)NULL) return ((SVCXPRT *)NULL); if ((transp = svc_tli_create(RPC_ANYFD, nconf, (struct t_bind *)NULL, 0, 0)) == (SVCXPRT *)NULL) { freenetconfigent(nconf); return ((SVCXPRT *)NULL); } prognum = 0x40000000; while (prognum < 0x60000000 && svc_reg(transp, prognum, version, dispatch, nconf) == 0) { prognum++; } freenetconfigent(nconf); if (prognum >= 0x60000000) { svc_destroy(transp); return ((SVCXPRT *)NULL); } *program = prognum; return (transp); } |