GSS-API のプログラミング

サーバー側アプリケーション

この節では、前述のクライアント関数からメッセージを受信するサーバー側アプリケーションについて説明します。

プログラムヘッダー

プログラムヘッダーはサーバープログラムの宣言部分です。また、コマンド行が間違っていた場合に構文を説明する関数もあります。ここでは、GSS-API が提供するデフォルトのセキュリティ機構を使用するように設定されています。


例 A–8 プログラムヘッダー


/*
 * 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()

main() はプログラムのエントリポイントです。次に、コマンド行構文を示します。



gss-server [-port port] [-d] [-mech mech] host service msg

コマンド行を解析した後、main() は (指定されていれば) 希望のセキュリティ機構名を OID に変換し、資格を獲得し、コンテキストを確立し、データを受信し、そして、必要であれば機構 OID を破棄します。


注 –

通常、アプリケーションは機構を設定せずに、GSS-API が提供するデフォルトを使用するべきです。



例 A–9 main()


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()

createMechOid() は、プログラムの完全性のためだけに示しています。通常はデフォルトの機構を使用するべきです (GSS_C_NULL_OID で指定)。


例 A–10 createMechOid()


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() は要求されたネットワークサービスの資格を獲得します。


例 A–11 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()

sign_server() はプログラムの中心部分です。server_establish_context() を呼び出してコンテキストを受け入れ、データを受信、ラップ解除、および検証し、クライアントに返送する MIC を生成します。最後にコンテキストを削除します。


例 A–12 sign_server()


/*
 * 関数: sign_server
 *
 * 目的: 「署名」サービスを実行する
 *
 * 引数:
 *
 *      s               (r) 接続が受け入れられた TCP ソケット
 *      service_name    (r) コンテキストを確立する GSS-API サービスの
 *                          ASCII 名
 *
 * 戻り値: エラーが発生した場合は -1
 *
 * 効果:
 *
 * sign_server はコンテキストを確立し、単一の署名要求を実行する
 *
 * 署名要求は単一の GSS-API ラップ済みトークンである。まず、
 * このトークンがラップ解除される。次に、署名ブロックが
 *  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()

server_establish_context() はコンテキスト確立ループの一部として gss_accept_sec_context() を呼び出します。


例 A–13 server_establish_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_a_socket() はクライアントとの転送接続を作成するだけの関数です。


例 A–14 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()

最後に、create_a_socket()gss_export_sec_context()gss_import_sec_context() がどのように機能するかを示す小さな関数です。この関数は実用性はなく、ここでは上記 2 つの GSS-API 関数がどのように使用されるかだけを示しています。


例 A–15 test_import_export_context()


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()

timeval_subtract()test_import_export_context() が使用する簡便な関数です。


例 A–16 timeval_subtract()


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);
}