この付録では、GSS-API を使用して安全なネットワーク接続を行う 2 つのサンプルアプリケーションのソースコードを示します。一方はクライアントで、もう一方はサーバーです。2 つのプログラムは実行時にベンチマークを表示するため、ユーザーは GSS-API が使用されていることを見ることができます。さらに、クライアントアプリケーションとサーバーアプリケーションが使用する補助的な関数もいくつか示します。便宜上、クライアント側アプリケーションとサーバー側アプリケーションに分けて説明しています。
各プログラムについての詳細は、第 2 章「GSS-API サンプルプログラムについての概略説明」を参照してください。
この節では、クライアント側プログラム gss_client について説明します。
プログラムヘッダーはクライアントプログラムの宣言部分です。また、コマンド行が間違っていた場合に構文を説明する関数もあります。
/* * Copyright 1994 by OpenVision Technologies, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appears in all copies and * that both that copyright notice and this permission notice appear in * supporting documentation, and that the name of OpenVision not be used * in advertising or publicity pertaining to distribution of the software * without specific, written prior permission. OpenVision makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <error.h> #include <sys/stat.h> #include <fcntl.h> #include <gssapi/gssapi.h> #include <gssapi/gssapi_ext.h> #include "gss-misc.h" /* ディスプレイ状態に必要な大域機構 oid、および資格の取得 */ gss_OID g_mechOid = GSS_C_NULL_OID; void usage() { fprintf(stderr, "Usage: gss-client [-port port] [-d]" " [-mech mechOid] host service msg\n"); exit(1); } |
main() はプログラムのエントリポイントです。次に、コマンド行構文を示します。
gss-client [-port port] [-d] [-mech mech] host service msg |
コマンド行を解析した後、main() は (指定されていれば) 該当するセキュリティ機構名を OID に変換し、安全な接続を確立し、そして、必要であれば、機構の OID を破棄します。
main() は標準ではない関数 gss_release_oid() を使用します。この関数は GSS-API のすべての実装でサポートされているわけではないため、可能な限り使用するべきではありません。アプリケーションは独自の機構を割り当てるのではなく、デフォルトの機構を使用するため (GSS_C_NULL_OID で指定)、gss_release_oid() 関数はあまり使用するべきではありません。ここで使用している理由は下位互換性のためと、この GSS-API 実装の全体を示すためです。
int main(argc, argv) int argc; char **argv; { /* char *service_name, *hostname, *msg; */ char *msg; char service_name[128]; char hostname[128]; char *mechanism = 0; u_short port = 4444; int use_file = 0; OM_uint32 deleg_flag = 0, min_stat; display_file = stdout; /* 引数を解析する */ argc--; argv++; while (argc) { if (strcmp(*argv, "-port") == 0) { argc--; argv++; if (!argc) usage(); port = atoi(*argv); } else if (strcmp(*argv, "-mech") == 0) { argc--; argv++; if (!argc) usage(); mechanism = *argv; } else if (strcmp(*argv, "-d") == 0) { deleg_flag = GSS_C_DELEG_FLAG; } else if (strcmp(*argv, "-f") == 0) { use_file = 1; } else break; argc--; argv++; } if (argc != 3) usage(); if (argc > 1) { strcpy(hostname, argv[0]); } else if (gethostname(hostname, sizeof(hostname)) == -1) { perror("gethostname"); exit(1); } if (argc > 2) { strcpy(service_name, argv[1]); strcat(service_name, "@"); strcat(service_name, hostname); } msg = argv[2]; if (mechanism) parse_oid(mechanism, &g_mechOid); if (call_server(hostname, port, g_mechOid, service_name, deleg_flag, msg, use_file) < 0) exit(1); if (g_mechOid != GSS_C_NULL_OID) (void) gss_release_oid(&min_stat, &gmechOid); return 0; } |
GSS-API が処理できるように、(指定されていれば) コマンド行で指定されたセキュリティ機構名を OID に変換します。
このようなサンプルを示していますが、可能な限り、独自の機構を指定するのではなく、GSS-API 実装が提供するデフォルトの機構を使用することを強く推奨します。デフォルトの機構を取得するには、機構 OID 値を GSS_C_NULL_OID に設定します。また、gss_str_to_oid() 関数はすべての GSS-API 実装でサポートされているわけではありません。
static void parse_oid(char *mechanism, gss_OID *oid) { char *mechstr = 0, *cp; gss_buffer_desc tok; OM_uint32 maj_stat, min_stat; if (isdigit(mechanism[0])) { mechstr = malloc(strlen(mechanism)+5); if (!mechstr) { printf("Couldn't allocate mechanism scratch!\n"); return; } sprintf(mechstr, "{ %s }", mechanism); for (cp = mechstr; *cp; cp++) if (*cp == '.') *cp = ' '; tok.value = mechstr; } else tok.value = mechanism; tok.length = strlen(tok.value); maj_stat = gss_str_to_oid(&min_stat, &tok, oid); if (maj_stat != GSS_S_COMPLETE) { display_status("str_to_oid", maj_stat, min_stat); return; } if (mechstr) free(mechstr); } |
call_server() はプログラムの中心部分です。
/* * 関数: call_server * * 目的: 「署名」サービスを呼び出す * * 引数: * * host (r) サービスを提供するホスト * port (r) ホストに接続するためのポート * service_name (r) 認証する GSS-API サービス名 * msg (r) 「署名」されたメッセージ * * 戻り値: 成功した場合は 0、失敗した場合は -1 * * 効果: * * call_server は <host:port> への TCP 接続を開き、その接続上で * service_name との GSS-API コンテキストを確立する。次に、gss_wrap で * msg を GSS-API トークンにラップし、サーバーに送信し、サーバーから * 戻された msg の GSS-API の署名ブロックを読み取り、gss_verify で検証する。 * どこかで失敗した場合は -1 を戻し、そうでない場合は 0 を戻す。 */ int call_server(host, port, oid, service_name, deleg_flag, msg, use_file) char *host; u_short port; gss_OID oid; char *service_name; OM_uint32 deleg_flag; char *msg; int use_file; { gss_ctx_id_t context; gss_buffer_desc in_buf, out_buf, context_token; int s, state; OM_uint32 ret_flags; OM_uint32 maj_stat, min_stat; gss_name_t src_name, targ_name; gss_buffer_desc sname, tname; OM_uint32 lifetime; gss_OID mechanism, name_type; int is_local; OM_uint32 context_flags; int is_open; gss_qop_t qop_state; gss_OID_set mech_names; gss_buffer_desc oid_name; int i; int conf_req_flag = 0; int req_output_size = 1012; OM_uint32 max_input_size = 0; char *mechStr; /* 接続を開く */ if ((s = connect_to_server(host, port)) < 0) return -1; /* 接続を確立する */ if (client_establish_context(s, service_name, deleg_flag, oid, &context, &ret_flags) < 0) { (void) close(s); return -1; } /* 保存後、コンテキストを復元する */ maj_stat = gss_export_sec_context(&min_stat, &context, &context_token); if (maj_stat != GSS_S_COMPLETE) { display_status("exporting context", maj_stat, min_stat); return -1; } maj_stat = gss_import_sec_context(&min_stat, &context_token, &context); if (maj_stat != GSS_S_COMPLETE) { display_status("importing context", maj_stat, min_stat); return -1; } (void) gss_release_buffer(&min_stat, &context_token); /* フラグを表示する */ display_ctx_flags(ret_flags); /* コンテキスト情報を取得する */ maj_stat = gss_inquire_context(&min_stat, context, &src_name, &targ_name, &lifetime, &mechanism, &context_flags, &is_local, &is_open); if (maj_stat != GSS_S_COMPLETE) { display_status("inquiring context", maj_stat, min_stat); return -1; } if (maj_stat == GSS_S_CONTEXT_EXPIRED) { printf(" context expired\n"); display_status("Context is expired", maj_stat, min_stat); return -1; } /* gss_wrap_size_limit をテストする */ maj_stat = gss_wrap_size_limit(&min_stat, context, conf_req_flag, GSS_C_QOP_DEFAULT, req_output_size, &max_input_size ); if (maj_stat != GSS_S_COMPLETE) { display_status("wrap_size_limit call", maj_stat, min_stat); } else fprintf (stderr, "gss_wrap_size_limit returned " "max input size = %d \n" "for req_output_size = %d with Integrity only\n", max_input_size , req_output_size , conf_req_flag); conf_req_flag = 1; maj_stat = gss_wrap_size_limit(&min_stat, context, conf_req_flag, GSS_C_QOP_DEFAULT, req_output_size, &max_input_size ); if (maj_stat != GSS_S_COMPLETE) { display_status("wrap_size_limit call", maj_stat, min_stat); } else fprintf (stderr, "gss_wrap_size_limit returned " " max input size = %d \n" "for req_output_size = %d with " "Integrity & Privacy \n", max_input_size , req_output_size ); maj_stat = gss_display_name(&min_stat, src_name, &sname, &name_type); if (maj_stat != GSS_S_COMPLETE) { display_status("displaying source name", maj_stat, min_stat); return -1; } maj_stat = gss_display_name(&min_stat, targ_name, &tname, (gss_OID *) NULL); if (maj_stat != GSS_S_COMPLETE) { display_status("displaying target name", maj_stat, min_stat); return -1; } fprintf(stderr, "\"%.*s\" to \"%.*s\", lifetime %u, flags %x, %s, %s\n", (int) sname.length, (char *) sname.value, (int) tname.length, (char *) tname.value, lifetime, context_flags, (is_local) ? "locally initiated" : "remotely initiated", (is_open) ? "open" : "closed"); (void) gss_release_name(&min_stat, &src_name); (void) gss_release_name(&min_stat, &targ_name); (void) gss_release_buffer(&min_stat, &sname); (void) gss_release_buffer(&min_stat, &tname); maj_stat = gss_oid_to_str(&min_stat, name_type, &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } fprintf(stderr, "Name type of source name is %.*s.\n", (int) oid_name.length, (char *) oid_name.value); (void) gss_release_buffer(&min_stat, &oid_name); /* この機構によってサポートされる名前を取得する */ maj_stat = gss_inquire_names_for_mech(&min_stat, mechanism, &mech_names); if (maj_stat != GSS_S_COMPLETE) { display_status("inquiring mech names", maj_stat, min_stat); return -1; } maj_stat = gss_oid_to_str(&min_stat, mechanism, &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } mechStr = (char *)__gss_oid_to_mech(mechanism); fprintf(stderr, "Mechanism %.*s (%s) supports %d names\n", (int) oid_name.length, (char *) oid_name.value, (mechStr == NULL ? "NULL" : mechStr), mech_names->count); (void) gss_release_buffer(&min_stat, &oid_name); for (i=0; i < mech_names->count; i++) { maj_stat = gss_oid_to_str(&min_stat, &mech_names->elements[i], &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } fprintf(stderr, " %d: %.*s\n", i, (int) oid_name.length, (char *) oid_name.value); (void) gss_release_buffer(&min_stat, &oid_name); } (void) gss_release_oid_set(&min_stat, &mech_names); if (use_file) { read_file(msg, &in_buf); } else { /* メッセージ内容を構造体へ設定完了 */ in_buf.value = msg; in_buf.length = strlen(msg) + 1; } if (ret_flag & GSS_C_CONF_FLAG) { state = 1; else state = 0; } maj_stat = gss_wrap(&min_stat, context, 1, GSS_C_QOP_DEFAULT, &in_buf, &state, &out_buf); if (maj_stat != GSS_S_COMPLETE) { display_status("wrapping message", maj_stat, min_stat); (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } else if (! state) { fprintf(stderr, "Warning! Message not encrypted.\n"); } /* サーバーに送信する */ if (send_token(s, &out_buf) < 0) { (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } (void) gss_release_buffer(&min_stat, &out_buf); /* 署名ブロックを out_buf に読み取る */ if (recv_token(s, &out_buf) < 0) { (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } /* 署名ブロックを検証する */ maj_stat = gss_verify_mic(&min_stat, context, &in_buf, &out_buf, &qop_state); if (maj_stat != GSS_S_COMPLETE) { display_status("verifying signature", maj_stat, min_stat); (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } (void) gss_release_buffer(&min_stat, &out_buf); if (use_file) free(in_buf.value); printf("Signature verified.\n"); /* コンテキストを削除する */ maj_stat = gss_delete_sec_context(&min_stat, &context, &out_buf); if (maj_stat != GSS_S_COMPLETE) { display_status("deleting context", maj_stat, min_stat); (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } (void) gss_release_buffer(&min_stat, &out_buf); (void) close(s); return 0; } |
転送されるメッセージがファイルに格納されている場合、read_file() 関数は (call_server() から呼び出されて) ファイルを開いて読み取ります。
void read_file(file_name, in_buf) char *file_name; gss_buffer_t in_buf; { int fd, bytes_in, count; struct stat stat_buf; if ((fd = open(file_name, O_RDONLY, 0)) < 0) { perror("open"); fprintf(stderr, "Couldn't open file %s\n", file_name); exit(1); } if (fstat(fd, &stat_buf) < 0) { perror("fstat"); exit(1); } in_buf->length = stat_buf.st_size; in_buf->value = malloc(in_buf->length); if (in_buf->value == 0) { fprintf(stderr, "Couldn't allocate %ld byte buffer for reading file\n", in_buf->length); exit(1); } memset(in_buf->value, 0, in_buf->length); for (bytes_in = 0; bytes_in < in_buf->length; bytes_in += count) { count = read(fd, in_buf->value, (OM_uint32)in_buf->length); if (count < 0) { perror("read"); exit(1); } if (count == 0) break; } if (bytes_in != count) fprintf(stderr, "Warning, only read in %d bytes, expected %d\n", bytes_in, count); } |
client_establish_context() は gss_init_sec_context() を呼び出して、サーバーとのコンテキストを確立します。
/* * 関数: client_establish_context * * 目的: 指定されたサービスとの GSS-API コンテキストを確立し、 * コンテキストハンドルを戻す * * 引数: * * s (r) サーバーとの間で確立された TCP 接続 * service_name (r) サービスの ASCII 名 * context (w) 確立された GSS-API コンテキスト * ret_flags (w) init_sec_context から戻されたフラグ * * 戻り値: 成功した場合は 0、失敗した場合は -1 * * 効果: * * service_name が GSS-API 名としてインポートされ、 * 対応するサービスとの間で GSS-API コンテキストが確立される。 * サービスは TCP 接続 s 上で応答待ちする。デフォルトの * GSS-API 機構が使用され、相互認証とリプレイの検出が * 要求される。 * * 成功した場合、コンテキストハンドルが context に戻される。 * 失敗した場合、GSS-API エラーメッセージが stderr に表示され、 * -1 が戻される。 */ int client_establish_context(s, service_name, deleg_flag, oid, gss_context, ret_flags) int s; char *service_name; gss_OID oid; OM_uint32 deleg_flag; gss_ctx_id_t *gss_context; OM_uint32 *ret_flags; { gss_buffer_desc send_tok, recv_tok, *token_ptr; gss_name_t target_name; OM_uint32 maj_stat, min_stat; /* * 名前を target_name にインポートする。send_tok で * ローカル変数空間を保存する。 */ send_tok.value = service_name; send_tok.length = strlen(service_name) + 1; maj_stat = gss_import_name(&min_stat, &send_tok, (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &target_name); if (maj_stat != GSS_S_COMPLETE) { display_status("parsing name", maj_stat, min_stat); return -1; } /* * コンテキスト確立ループを実行する。 * * ループを通過するごとに、token_ptr はサーバーに送信される * トークンに設定される (最初に通過するときは GSS_C_NO_BUFFER)。 * 生成された各トークンは send_tok に格納され、サーバーに送信される。 * 受信された各トークンは recv_tok に格納され、token_ptr は次の * gss_init_sec_context の呼び出しで処理されるトークンに * 設定される。 * * GSS-API は、以下の 2 つのことを保証する。 * 1.サーバーがクライアントからこれ以上トークンを期待していない場合のみ、 * send_tok の length の値が 0 になる。 * 2.サーバーにクライアントへ送るトークンが存在する場合のみ、 * gss_init_sec_context が GSS_S_CONTINUE_NEEDED を戻す。 */ token_ptr = GSS_C_NO_BUFFER; *gss_context = GSS_C_NO_CONTEXT; do { maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, gss_context, target_name, oid, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | deleg_flag, 0, NULL, /* チャネルバインディングなし */ token_ptr, NULL, /* 機構の型を無視する */ &send_tok, ret_flags, NULL); /* time_rec を無視する */ if (gss_context == NULL){ printf("Cannot create context\n"); return GSS_S_NO_CONTEXT; } if (token_ptr != GSS_C_NO_BUFFER) (void) gss_release_buffer(&min_stat, &recv_tok); if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) { display_status("initializing context", maj_stat, min_stat); (void) gss_release_name(&min_stat, &target_name); return -1; } if (send_tok.length != 0) { fprintf(stdout, "Sending init_sec_context token (size=%ld)...", send_tok.length); if (send_token(s, &send_tok) < 0) { (void) gss_release_buffer(&min_stat, &send_tok); (void) gss_release_name(&min_stat, &target_name); return -1; } } (void) gss_release_buffer(&min_stat, &send_tok); if (maj_stat == GSS_S_CONTINUE_NEEDED) { fprintf(stdout, "continue needed..."); if (recv_token(s, &recv_tok) < 0) { (void) gss_release_name(&min_stat, &target_name); return -1; } token_ptr = &recv_tok; } printf("\n"); } while (maj_stat == GSS_S_CONTINUE_NEEDED); (void) gss_release_name(&min_stat, &target_name); return 0; } |
connect_to_server() は TCP 接続を作成するだけの基本的な関数です。
/* * 関数: connect_to_server * * 目的: 指定されたホストとポートへの TCP 接続を開く * * 引数: * * host (r) ターゲットのホスト名 * port (r) ターゲットのポート。ホストのバイト順。 * * 戻り値: 成功した場合は、確立されたソケットのファイル記述子。 * 失敗した場合は -1。 * * 効果: * * ホスト名が gethostbyname() で解釈処理され、ソケットが開かれ、 * 接続される。エラーが発生した場合、エラーメッセージが表示され、 * -1 が戻される。 */ int connect_to_server(host, port) char *host; u_short port; { struct sockaddr_in saddr; struct hostent *hp; int s; if ((hp = gethostbyname(host)) == NULL) { fprintf(stderr, "Unknown host: %s\n", host); return -1; } saddr.sin_family = hp->h_addrtype; memcpy((char *)&saddr.sin_addr, hp->h_addr, sizeof(saddr.sin_addr)); saddr.sin_port = htons(port); if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("creating socket"); return -1; } if (connect(s, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) { perror("connecting to server"); (void) close(s); return -1; } return s; } |
この節では、前述のクライアント関数からメッセージを受信するサーバー側アプリケーションについて説明します。
プログラムヘッダーはサーバープログラムの宣言部分です。また、コマンド行が間違っていた場合に構文を説明する関数もあります。ここでは、GSS-API が提供するデフォルトのセキュリティ機構を使用するように設定されています。
/* * Copyright 1994 by OpenVision Technologies, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appears in all copies and * that both that copyright notice and this permission notice appear in * supporting documentation, and that the name of OpenVision not be used * in advertising or publicity pertaining to distribution of the software * without specific, written prior permission. OpenVision makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #if !defined(lint) && !defined(__CODECENTER__) static char *rcsid = "$Header: /afs/athena.mit.edu/astaff/project/krbdev/.cvsroot /src/appl/gss-sample/gss-server.c,v 1.17 1996/10/22 00:07:59 tytso Exp $"; #endif #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #ifdef HAVE_UNISTD_H #include <unistd.h> #endif #include <stdlib.h> #include <ctype.h> #include <gssapi/gssapi.h> #include <gssapi/gssapi_ext.h> #include "gss-misc.h" #ifdef USE_STRING_H #include <string.h> #else #include <strings.h> #endif /* 資格の獲得と状態の表示で使用される大域的な機構 OID */ gss_OID g_mechOid = GSS_C_NULL_OID; void usage() { fprintf(stderr, "Usage: gss-server [-port port] [-verbose]\n"); fprintf(stderr, " [-inetd] [-logfile file]"); fprintf(stderr, " [-mech mechoid] [service_name]\n"); exit(1); } FILE *log; int verbose = 0; |
main() はプログラムのエントリポイントです。次に、コマンド行構文を示します。
gss-server [-port port] [-d] [-mech mech] host service msg |
コマンド行を解析した後、main() は (指定されていれば) 希望のセキュリティ機構名を OID に変換し、資格を獲得し、コンテキストを確立し、データを受信し、そして、必要であれば機構 OID を破棄します。
通常、アプリケーションは機構を設定せずに、GSS-API が提供するデフォルトを使用するべきです。
int main(argc, argv) int argc; char **argv; { char *service_name, *mechType = NULL; gss_cred_id_t server_creds; OM_uint32 min_stat; u_short port = 4444; int s; int once = 0; int do_inetd = 0; log = stdout; display_file = stdout; argc--; argv++; while (argc) { if (strcmp(*argv, "-port") == 0) { argc--; argv++; if (!argc) usage(); port = atoi(*argv); } else if (strcmp(*argv, "-verbose") == 0) { verbose = 1; } else if (strcmp(*argv, "-once") == 0) { once = 1; } else if (strcmp(*argv, "-inetd") == 0) { do_inetd = 1; } else if (strcmp(*argv, "-mech") == 0) { argc--; argv++; if (!argc) usage(); mechType = *argv; } else if (strcmp(*argv, "-logfile") == 0) { argc--; argv++; if (!argc) usage(); log = fopen(*argv, "a"); display_file = log; if (!log) { perror(*argv); exit(1); } } else break; argc--; argv++; } if (argc != 1) usage(); if ((*argv)[0] == '-') usage(); service_name = *argv; if (mechType != NULL) { if ((g_mechOid = createMechOid(mechType)) == NULL) { usage(); exit(-1); } } if (server_acquire_creds(service_name, g_mechOid, &server_creds) < 0) return -1; if (do_inetd) { close(1); close(2); sign_server(0, server_creds); close(0); } else { int stmp; if ((stmp = create_socket(port))) { do { /* TCP 接続を受け入れる */ if ((s = accept(stmp, NULL, 0)) < 0) { perror("accepting connection"); } else { /* 失敗したとしても有効な対処はここではできないため、 この戻り値は検査されない */ sign_server(s, server_creds); } } while (!once); } close(stmp); } (void) gss_release_cred(&min_stat, &server_creds); if (g_mechOid != GSS_C_NULL_OID) gss_release_oid(&min_stat, &g_mechOid); /*NOTREACHED*/ (void) close(s); return 0; } |
createMechOid() は、プログラムの完全性のためだけに示しています。通常はデフォルトの機構を使用するべきです (GSS_C_NULL_OID
で指定)。
gss_OID createMechOid(const char *mechStr) { gss_buffer_desc mechDesc; gss_OID mechOid; OM_uint32 minor; if (mechStr == NULL) return (GSS_C_NULL_OID); mechDesc.length = strlen(mechStr); mechDesc.value = (void *) mechStr; if (gss_str_to_oid(&minor, &mechDesc, &mechOid) ! = GSS_S_COMPLETE) { fprintf(stderr, "Invalid mechanism oid specified <%s>", mechStr); return (GSS_C_NULL_OID); } return (mechOid); } |
server_acquire_creds() は要求されたネットワークサービスの資格を取得します。
/* * 関数: server_acquire_creds * * 目的: サービス名をインポートし、その資格を獲得する * * 引数: * * service_name (r) サービスの ASCII 名 mechType (r) 使用される機構の型 * server_creds (w) GSS-API サービスの資格 * * 戻り値: 成功した場合は 0、失敗した場合は -1 * * 効果: * * サービス名が gss_import_name でインポートされ、サービスの資格が * gss_acquire_cred で獲得される。どちらかの操作が失敗した場合、 * エラーメッセージが表示され、-1 が戻される。そうでない場合、0 が戻される。 */ int server_acquire_creds(service_name, mechOid, server_creds) char *service_name; gss_OID mechOid; gss_cred_id_t *server_creds; { gss_buffer_desc name_buf; gss_name_t server_name; OM_uint32 maj_stat, min_stat; gss_OID_set_desc mechOidSet; gss_OID_set desiredMechs = GSS_C_NULL_OID_SET; if (mechOid != GSS_C_NULL_OID) { desiredMechs = &mechOidSet; mechOidSet.count = 1; mechOidSet.elements = mechOid; } else desiredMechs = GSS_C_NULL_OID_SET; name_buf.value = service_name; name_buf.length = strlen(name_buf.value) + 1; maj_stat = gss_import_name(&min_stat, &name_buf, (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name); if (maj_stat != GSS_S_COMPLETE) { display_status("importing name", maj_stat, min_stat); if (mechOid != GSS_C_NO_OID) gss_release_oid(&min_stat, &mechOid); return -1; } maj_stat = gss_acquire_cred(&min_stat, server_name, 0, desiredMechs, GSS_C_ACCEPT, server_creds, NULL, NULL); if (maj_stat != GSS_S_COMPLETE) { display_status("acquiring credentials", maj_stat, min_stat); return -1; } (void) gss_release_name(&min_stat, &server_name); return 0; } |
sign_server() はプログラムの中心部分です。server_establish_context() を呼び出してコンテキストを受け入れ、データを受信、ラップ解除、および検証し、そしてクライアントに返送する MIC を生成します。最後に、コンテキストを削除します。
/* * 関数: sign_server * * 目的: 「署名」サービスを実行する * * 引数: * * s (r) 接続が受け入れられた TCP ソケット * service_name (r) コンテキストを確立する GSS-API サービスの ASCII 名 * * 戻り値: エラーが発生した場合は -1 * * 効果: * * sign_server はコンテキストを確立し、単一の署名要求を実行する * * 署名要求は単一の GSS-AP ラップ済みトークンである。まず、 * このトークンがラップ解除される。次に、署名ブロックが * gss_get_mic で生成され、送信側に戻される。コンテキストが * 破棄され、接続が閉じられる。 * * エラーが発生した場合、-1 が戻される。 */ int sign_server(s, server_creds) int s; gss_cred_id_t server_creds; { gss_buffer_desc client_name, xmit_buf, msg_buf; gss_ctx_id_t context; OM_uint32 maj_stat, min_stat; int i, conf_state, ret_flags; char *cp; /* クライアントとのコンテキストを確立する */ if (server_establish_context(s, server_creds, &context, &client_name, &ret_flags) < 0) return(-1); printf("Accepted connection: \"%.*s\"\n", (int) client_name.length, (char *) client_name.value); (void) gss_release_buffer(&min_stat, &client_name); for (i=0; i < 3; i++) if (test_import_export_context(&context)) return -1; /* ラップ済みメッセージトークンを受信する */ if (recv_token(s, &xmit_buf) < 0) return(-1); if (verbose && log) { fprintf(log, "Wrapped message token:\n"); print_token(&xmit_buf); } maj_stat = gss_unwrap(&min_stat, context, &xmit_buf, &msg_buf, &conf_state, (gss_qop_t *) NULL); if (maj_stat != GSS_S_COMPLETE) { display_status("unwrapping message", maj_stat, min_stat); return(-1); } else if (! conf_state) { fprintf(stderr, "Warning! Message not encrypted.\n"); } (void) gss_release_buffer(&min_stat, &xmit_buf); fprintf(log, "Received message: "); cp = msg_buf.value; if (isprint(cp[0]) && isprint(cp[1])) fprintf(log, "\"%s\"\n", cp); else { printf("\n"); print_token(&msg_buf); } /* メッセージの署名ブロックを生成する */ maj_stat = gss_get_mic(&min_stat, context, GSS_C_QOP_DEFAULT, &msg_buf, &xmit_buf); if (maj_stat != GSS_S_COMPLETE) { display_status("signing message", maj_stat, min_stat); return(-1); } (void) gss_release_buffer(&min_stat, &msg_buf); /* 署名ブロックをクライアントに送信する */ if (send_token(s, &xmit_buf) < 0) return(-1); (void) gss_release_buffer(&min_stat, &xmit_buf); /* コンテキストを削除する */ maj_stat = gss_delete_sec_context(&min_stat, &context, NULL); if (maj_stat != GSS_S_COMPLETE) { display_status("deleting context", maj_stat, min_stat); return(-1); } fflush(log); return(0); } |
server_establish_context() はコンテキスト確立ループの一部として gss_accept_sec_context() を呼び出します。
/* * 関数: server_establish_context * * 目的: 着信クライアントで指定されたサービスとして * GSS-API コンテキストを確立し、コンテキストハンドルと * 関連するクライアント名を戻す。 * * 引数: * * s (r) クライアントとの間で確立された TCP 接続 * service_creds (r) gss_acquire_cred で取得したサーバーの資格 * context (w) 確立された GSS-API コンテキスト * client_name (w) クライアントの ASCII 名 * * 戻り値: 成功した場合は 0、失敗した場合は -1 * * 効果: * * 有効なクライアント要求はすべて受け入れられる。コンテキストが * 確立された場合、そのハンドルが context に戻され、クライアント名が * client_name に戻され、0 が戻される。失敗した場合、エラーメッセージが * 表示され、-1 が戻される。 */ int server_establish_context(s, server_creds, context, client_name, ret_flags) int s; gss_cred_id_t server_creds; gss_ctx_id_t *context; gss_buffer_t client_name; OM_uint32 *ret_flags; { gss_buffer_desc send_tok, recv_tok; gss_name_t client; gss_OID doid; OM_uint32 maj_stat, min_stat; gss_buffer_desc oid_name; char *mechStr; *context = GSS_C_NO_CONTEXT; do { if (recv_token(s, &recv_tok) < 0) return -1; if (verbose && log) { fprintf(log, "Received token (size=%d): \n", recv_tok.length); print_token(&recv_tok); } maj_stat = gss_accept_sec_context(&min_stat, context, server_creds, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &client, &doid, &send_tok, ret_flags, NULL, /* time_rec を無視する */ NULL); /* del_cred_handle を無視する */ if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) { display_status("accepting context", maj_stat, min_stat); (void) gss_release_buffer(&min_stat, &recv_tok); return -1; } (void) gss_release_buffer(&min_stat, &recv_tok); if (send_tok.length != 0) { if (verbose && log) { fprintf(log, "Sending accept_sec_context token (size=%d):\n", send_tok.length); print_token(&send_tok); } if (send_token(s, &send_tok) < 0) { fprintf(log, "failure sending token\n"); return -1; } (void) gss_release_buffer(&min_stat, &send_tok); } if (verbose && log) { if (maj_stat == GSS_S_CONTINUE_NEEDED) fprintf(log, "continue needed...\n"); else fprintf(log, "\n"); fflush(log); } } while (maj_stat == GSS_S_CONTINUE_NEEDED); /* フラグを表示する */ display_ctx_flags(*ret_flags); if (verbose && log) { maj_stat = gss_oid_to_str(&min_stat, doid, &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } mechStr = (char *)__gss_oid_to_mech(doid); fprintf(log, "Accepted connection using mechanism OID %.*s (%s).\n", (int) oid_name.length, (char *) oid_name.value, (mechStr == NULL ? "NULL" : mechStr)); (void) gss_release_buffer(&min_stat, &oid_name); } maj_stat = gss_display_name(&min_stat, client, client_name, &doid); if (maj_stat != GSS_S_COMPLETE) { display_status("displaying name", maj_stat, min_stat); return -1; } return 0; } |
create_a_socket() はクライアントとの転送接続を作成するだけの関数です。
/* * 関数: create_socket * * 目的: 応答待ちする TCP ソケットを開く * * 引数: * * port (r) 応答待ちするポート番号 * * 戻り値: 成功した場合は、応答待ちするソケットのファイル記述子。 * 失敗した場合は -1。 * * 効果: * * 指定されたポート上で応答待ちするソケットが作成され、そのファイル記述子が * 戻される。エラーが発生した場合、エラーメッセージが表示され、-1 が戻される。 */ int create_socket(port) u_short port; { struct sockaddr_in saddr; int s; int on = 1; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); saddr.sin_addr.s_addr = INADDR_ANY; if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("creating socket"); return -1; } /* ソケットを再使用できるようにする */ (void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); if (bind(s, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { perror("binding socket"); (void) close(s); return -1; } if (listen(s, 5) < 0) { perror("listening on socket"); (void) close(s); return -1; } return s; } |
最後に、test_import_export_context() は gss_export_sec_context() と gss_import_sec_context() がどのように機能するかを示す小さな関数です。この関数は実用性はなく、ここでは上記 2 つの GSS-API 関数がどのように使用されるかだけを示しています。
int test_import_export_context(context) gss_ctx_id_t *context; { OM_uint32 min_stat, maj_stat; gss_buffer_desc context_token, copied_token; struct timeval tm1, tm2; /* * 保存後、コンテキストを復元する */ gettimeofday(&tm1, (struct timezone *)0); maj_stat = gss_export_sec_context(&min_stat, context, &context_token); if (maj_stat != GSS_S_COMPLETE) { display_status("exporting context", maj_stat, min_stat); return 1; } gettimeofday(&tm2, (struct timezone *)0); if (verbose && log) fprintf(log, "Exported context: %d bytes, %7.4f seconds\n", context_token.length, timeval_subtract(&tm2, &tm1)); copied_token.length = context_token.length; copied_token.value = malloc(context_token.length); if (copied_token.value == 0) { fprintf(log, "Couldn't allocate memory to copy context token.\n"); return 1; } memcpy(copied_token.value, context_token.value, copied_token.length); maj_stat = gss_import_sec_context(&min_stat, &copied_token, context); if (maj_stat != GSS_S_COMPLETE) { display_status("importing context", maj_stat, min_stat); return 1; } gettimeofday(&tm1, (struct timezone *)0); if (verbose && log) fprintf(log, "Importing context: %7.4f seconds\n", timeval_subtract(&tm1, &tm2)); (void) gss_release_buffer(&min_stat, &context_token); return 0; } |
timeval_subtract() は test_import_export_context() が使用する簡便な関数です。
static float timeval_subtract(tv1, tv2) struct timeval *tv1, *tv2; { return ((tv1->tv_sec - tv2->tv_sec) + ((float) (tv1->tv_usec - tv2->tv_usec)) / 1000000); } |
クライアントプログラムとサーバープログラムが期待どおりに機能するには、他にもいくつかの関数が必要です。このような関数はほとんどが値を表示したりするもので、プログラムの基本機能には必ずしも必要ではありません。ここではプログラムの完全性のためだけに示しています。
ただし、2 つの関数 send_token() と recv_token() は重要です。この 2 つの関数はコンテキストのトークンとメッセージを実際に転送します。この 2 つの関数は純粋に基本的な関数であり、ファイル記述子を開いて読み書きします。普通でしかも GSS-API には直接関係はありませんが、別に説明するだけの価値はあります。
次に、さまざまなサポート関数について説明します。
display_status() — 最後に呼び出した GSS-API 関数から戻された状態を表示します。
write_all() — バッファをファイルに書き込みます。
read_all() — ファイルからバッファに読み取ります。
display_ctx_flags() — 現在のコンテキストについての情報を人が読める形式で表示します。たとえば、機密性または相互認証が許可されているかどうかなどです。
print_token() — トークンの値を出力します。
/* * Copyright 1994 by OpenVision Technologies, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appears in all copies and * that both that copyright notice and this permission notice appear in * supporting documentation, and that the name of OpenVision not be used * in advertising or publicity pertaining to distribution of the software * without specific, written prior permission. OpenVision makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #if !defined(lint) && !defined(__CODECENTER__) static char *rcsid = "$Header: /afs/athena.mit.edu/astaff/project/krbdev/.cvsroot /src/appl/gss-sample/gss-misc.c,v 1.15 1996/07/22 20:21:20 marc Exp $"; #endif #include <stdio.h> #include <sys/types.h> #include <netinet/in.h> #include <errno.h> #ifdef HAVE_UNISTD_H #include <unistd.h> #endif #include <string.h> #include <gssapi/gssapi.h> #include "gss-misc.h" #include <stdlib.h> FILE *display_file; extern gss_OID g_mechOid; static void display_status_1(char *m, OM_uint32 code, int type); static int write_all(int fildes, char *buf, unsigned int nbyte) { int ret; char *ptr; for (ptr = buf; nbyte; ptr += ret, nbyte -= ret) { ret = write(fildes, ptr, nbyte); if (ret < 0) { if (errno == EINTR) continue; return(ret); } else if (ret == 0) { return(ptr-buf); } } return(ptr-buf); } static int read_all(int fildes, char *buf, unsigned int nbyte) { int ret; char *ptr; for (ptr = buf; nbyte; ptr += ret, nbyte -= ret) { ret = read(fildes, ptr, nbyte); if (ret < 0) { if (errno == EINTR) continue; return(ret); } else if (ret == 0) { return(ptr-buf); } } return(ptr-buf); } static void display_status_1(m, code, type) char *m; OM_uint32 code; int type; { OM_uint32 maj_stat, min_stat; gss_buffer_desc msg = GSS_C_EMPTY_BUFFER; OM_uint32 msg_ctx; msg_ctx = 0; while (1) { maj_stat = gss_display_status(&min_stat, code, type, g_mechOid, &msg_ctx, &msg); if (maj_stat != GSS_S_COMPLETE) { if (display_file) { fprintf(display_file, "error in gss_display_status" " called from <%s>\n", m); } break; } else if (display_file) fprintf(display_file, "GSS-API error %s: %s\n", m, (char *)msg.value); if (msg.length != 0) (void) gss_release_buffer(&min_stat, &msg); if (!msg_ctx) break; } } /* * 関数: display_status * * 目的: GSS-API メッセージを表示する * * 引数: * * msg メッセージと一緒に表示する文字列 * maj_stat GSS-API メジャー状態コード * min_stat GSS-API マイナー状態コード * * 効果: * * maj_stat と min_stat に関連する GSS-API メッセージが stderr に表示される。 * 各メッセージの前には「GSS-API error <msg>:」が、後には復帰改行が * 出力される。 */ void display_status(msg, maj_stat, min_stat) char *msg; OM_uint32 maj_stat; OM_uint32 min_stat; { display_status_1(msg, maj_stat, GSS_C_GSS_CODE); display_status_1(msg, min_stat, GSS_C_MECH_CODE); } /* * 関数: display_ctx_flags * * 目的: コンテキスト起動から戻されたフラグを人が読める形式で表示する。 * * 引数: * * int ret_flags * * 効果: * * コンテキストフラグに対応する文字列が stdout に出力される。各文字列の * 前には「context flag: 」が、後には復帰改行が出力される。 */ void display_ctx_flags(flags) OM_uint32 flags; { if (flags & GSS_C_DELEG_FLAG) fprintf(display_file, "context flag: GSS_C_DELEG_FLAG\n"); if (flags & GSS_C_MUTUAL_FLAG) fprintf(display_file, "context flag: GSS_C_MUTUAL_FLAG\n"); if (flags & GSS_C_REPLAY_FLAG) fprintf(display_file, "context flag: GSS_C_REPLAY_FLAG\n"); if (flags & GSS_C_SEQUENCE_FLAG) fprintf(display_file, "context flag: GSS_C_SEQUENCE_FLAG\n"); if (flags & GSS_C_CONF_FLAG ) fprintf(display_file, "context flag: GSS_C_CONF_FLAG \n"); if (flags & GSS_C_INTEG_FLAG ) fprintf(display_file, "context flag: GSS_C_INTEG_FLAG \n"); } void print_token(tok) gss_buffer_t tok; { int i; unsigned char *p = tok->value; if (!display_file) return; for (i=0; i < tok->length; i++, p++) { fprintf(display_file, "%02x ", *p); if ((i % 16) == 15) { fprintf(display_file, "\n"); } } fprintf(display_file, "\n"); fflush(display_file); } |
この 2 つの関数はクライアントとサーバー間でデータを送受信します。なお、マルチプロセスアプリケーションでは、プロセス間でデータを送受信できます。トークンと同様にメッセージも送受信するため、名前が若干間違っているように思えるかもしれません。この 2 つの関数は自分が処理する内容を意識しません。
send_token() はトークンまたはメッセージを送信します。
/* * 関数: send_token * * 目的: トークンをファイル記述子に書き込む * * 引数: * * s (r) 開いたファイル記述子 * tok (r) 書き込むトークン * * 戻り値: 成功した場合は 0、失敗した場合は -1 * * 効果: * * send_token はまずトークンの長さ (ネットワーク上の長さ) を、次にトークンの * データをファイル記述子 s に書き込みます。成功した場合は 0 を戻し、 * エラーが発生したり、すべてのデータを書き込むことができなかった場合は * -1 を戻します。 */ int send_token(s, tok) int s; gss_buffer_t tok; { int len, ret; len = htonl((OM_uint32)tok->length); ret = write_all(s, (char *) &len, sizeof(int)); if (ret < 0) { perror("sending token length"); return -1; } else if (ret != 4) { if (display_file) fprintf(display_file, "sending token length: %d of %d bytes written\n", ret, 4); return -1; } ret = write_all(s, tok->value, (OM_uint32)tok->length); if (ret < 0) { perror("sending token data"); return -1; } else if (ret != tok->length) { if (display_file) fprintf(display_file, "sending token data: %d of %d bytes written\n", ret, tok->length); return -1; } return 0; } |
recv_token() はトークンまたはメッセージを受信します。
/* * 関数: recv_token * * 目的: ファイル記述子からトークンを読み取ります * * 引数: * * s (r) 開いたファイル記述子 * tok (w) 読み取るトークン * * 戻り値: 成功した場合は 0、失敗した場合は -1 * * 効果: * * recv_token はまずトークンの長さ (ネットワーク上の長さ) を読み取り、その * データを保持するメモリーを割り当て、そして、ファイル記述子 s からトークン * のデータを読み取る。必要であれば、長さとデータの読みとりを中止する。 * 成功した場合、gss_release_buffer でトークンを解放する必要がある。 * 成功した場合は 0 を戻し、エラーが発生したり、すべてのデータを読み取る * ことができなかった場合は -1 を戻す。 */ int recv_token(s, tok) int s; gss_buffer_t tok; { int ret, len; ret = read_all(s, (char *) &len, sizeof(int)); if (ret < 0) { perror("reading token length"); return -1; } else if (ret != 4) { if (display_file) fprintf(display_file, "reading token length: %d of %d bytes read\n", ret, 4); return -1; } tok->length = ntohl(len); tok->value = (char *) malloc(tok->length); if (tok->value == NULL) { if (display_file) fprintf(display_file, "Out of memory allocating token data\n"); return -1; } ret = read_all(s, (char *) tok->value, (OM_uint32)tok->length); if (ret < 0) { perror("reading token data"); free(tok->value); return -1; } else if (ret != tok->length) { fprintf(stderr, "sending token data: %d of %d bytes written\n", ret, tok->length); free(tok->value); return -1; } return 0; } |