multithreaded gssapi test prog based on gss-sample w/jaltman+raeburn changes
authorKen Raeburn <raeburn@mit.edu>
Wed, 9 Feb 2005 22:37:38 +0000 (22:37 +0000)
committerKen Raeburn <raeburn@mit.edu>
Wed, 9 Feb 2005 22:37:38 +0000 (22:37 +0000)
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@17096 dc483132-0cff-0310-8789-dd5450dbe970

src/tests/gss-threads/ChangeLog [new file with mode: 0644]
src/tests/gss-threads/Makefile.in [new file with mode: 0644]
src/tests/gss-threads/README [new file with mode: 0644]
src/tests/gss-threads/gss-client.c [new file with mode: 0644]
src/tests/gss-threads/gss-misc.c [new file with mode: 0644]
src/tests/gss-threads/gss-misc.h [new file with mode: 0644]
src/tests/gss-threads/gss-server.c [new file with mode: 0644]

diff --git a/src/tests/gss-threads/ChangeLog b/src/tests/gss-threads/ChangeLog
new file mode 100644 (file)
index 0000000..2b4c175
--- /dev/null
@@ -0,0 +1,6 @@
+2005-02-09  Ken Raeburn  <raeburn@mit.edu>
+
+       * New directory.
+       * All files copies from appl/gss-sample, with Jeff Altman's
+       changes for multithreading on Windows, and mine for pthreads.
+
diff --git a/src/tests/gss-threads/Makefile.in b/src/tests/gss-threads/Makefile.in
new file mode 100644 (file)
index 0000000..1229081
--- /dev/null
@@ -0,0 +1,50 @@
+# Derived from appl/gss-sample, January 2005.
+
+thisconfigdir=./..
+myfulldir=tests/gss-threads
+mydir=gss-threads
+BUILDTOP=$(REL)..$(S)..
+DEFINES = -DUSE_AUTOCONF_H -DGSSAPI_V2
+PROG_LIBPATH=-L$(TOPLIBD)
+PROG_RPATH=$(KRB5_LIBDIR)
+PTHREAD_LIBS=@PTHREAD_LIBS@
+
+SRCS= $(srcdir)/gss-client.c $(srcdir)/gss-misc.c $(srcdir)/gss-server.c
+
+OBJS= gss-client.o gss-misc.o gss-server.o
+
+all-unix:: gss-server gss-client
+all-windows:: $(OUTPRE)gss-server.exe $(OUTPRE)gss-client.exe
+
+gss-server: gss-server.o gss-misc.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+       $(CC_LINK) $(PTHREAD_CFLAGS) -o gss-server gss-server.o gss-misc.o $(GSS_LIBS) $(KRB5_BASE_LIBS) $(PTHREAD_LIBS)
+
+gss-client: gss-client.o gss-misc.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+       $(CC_LINK) $(PTHREAD_CFLAGS) -o gss-client gss-client.o gss-misc.o $(GSS_LIBS) $(KRB5_BASE_LIBS) $(PTHREAD_LIBS)
+
+$(OUTPRE)gss-server.exe: $(OUTPRE)gss-server.obj $(OUTPRE)gss-misc.obj $(GLIB) $(KLIB)
+       link $(EXE_LINKOPTS) -out:$@ $** ws2_32.lib
+
+$(OUTPRE)gss-client.exe: $(OUTPRE)gss-client.obj $(OUTPRE)gss-misc.obj $(GLIB) $(KLIB)
+       link $(EXE_LINKOPTS) -out:$@ $** ws2_32.lib
+
+clean-unix::
+       $(RM) gss-server gss-client
+
+install-unix::
+#      $(INSTALL_PROGRAM) gss-client $(DESTDIR)$(CLIENT_BINDIR)/gss-tclient
+#      $(INSTALL_PROGRAM) gss-server $(DESTDIR)$(SERVER_BINDIR)/gss-tserver
+# +++ Dependency line eater +++
+# 
+# Makefile dependencies follow.  This must be the last section in
+# the Makefile.in file
+#
+$(OUTPRE)gss-client.$(OBJEXT): gss-client.c $(BUILDTOP)/include/gssapi/gssapi_generic.h \
+  $(BUILDTOP)/include/gssapi/gssapi.h gss-misc.h $(SRCTOP)/include/port-sockets.h \
+  $(BUILDTOP)/include/krb5/autoconf.h $(SRCTOP)/include/fake-addrinfo.h \
+  $(SRCTOP)/include/socket-utils.h
+$(OUTPRE)gss-misc.$(OBJEXT): gss-misc.c $(BUILDTOP)/include/gssapi/gssapi_generic.h \
+  $(BUILDTOP)/include/gssapi/gssapi.h gss-misc.h
+$(OUTPRE)gss-server.$(OBJEXT): gss-server.c $(BUILDTOP)/include/gssapi/gssapi_generic.h \
+  $(BUILDTOP)/include/gssapi/gssapi.h gss-misc.h $(SRCTOP)/include/port-sockets.h \
+  $(BUILDTOP)/include/krb5/autoconf.h
diff --git a/src/tests/gss-threads/README b/src/tests/gss-threads/README
new file mode 100644 (file)
index 0000000..f555b3e
--- /dev/null
@@ -0,0 +1,165 @@
+[Out of date; needs updating for thread safety test support.  -- KR 2005-02-09]
+
+# Copyright 1993 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.
+
+This directory contains a sample GSS-API client and server
+application.  In addition to serving as an example of GSS-API
+programming, this application is also intended to be a tool for
+testing the performance of GSS-API implementations.
+
+Each time the client is invoked, it performs one or more exchanges
+with the server.  Each exchange with the server consists primarily of
+the following steps:
+
+       1. A TCP/IP connection is established.
+
+       2. (optional, on by default) The client and server establish a
+          GSS-API context, and the server prints the identify of the
+          client.
+
+      /        3. The client sends a message to the server.  The message may
+     /     be plaintext, cryptographically "signed" but not encrypted,
+     |     or encrypted (default).
+     |
+0 or |  4. The server decrypts the message (if necessary), verifies
+more |     its signature (if there is one) and prints it.
+times|
+     |  5. The server sends either a signature block (the default) or an
+     |     empty token back to the client to acknowledge the message.
+     \
+      \ 6. If the server sent a signature block, the client verifies
+           it and prints a message indicating that it was verified.
+  
+       7. The client sends an empty block to the server to tell it
+          that the exchange is finished.
+  
+       8. The client and server close the TCP/IP connection and
+          destroy the GSS-API context.
+
+The client also supports the -v1 flag which uses an older exchange
+format compatible with previous releases of Kerberos and with samples
+shipped in the Microsoft SDK.
+  
+The server's command line usage is
+  
+       gss-server [-port port] [-verbose] [-once] [-inetd] [-export]
+               [-logfile file] service_name
+  
+where service_name is a GSS-API service name of the form
+"service@host" (or just "service", in which case the local host name
+is used).  The command-line options have the following meanings:
+  
+-port  The TCP port on which to accept connections.  Default is 4444.
+  
+-once  Tells the server to exit after a single exchange, rather than
+       persisting.
+  
+-inetd Tells the server that it is running out of inetd, so it should
+       interact with the client on stdin rather than binding to a
+       network port.  Implies "-once".
+  
+-export        Tells the server to test the gss_export_sec_context function
+       after establishing a context with a client.
+
+-logfile
+       The file to which the server should append its output, rather
+       than sending it to stdout.
+  
+The client's command line usage is
+
+       gss-client [-port port] [-mech mechanism] [-d] [-f] [-q]
+        [-seq] [-noreplay] [-nomutual]         
+        [-ccount count] [-mcount count] [-na] [-nw] [-nx] [-nm]
+               host service_name msg
+
+where host is the host running the server, service_name is the service
+name that the server will establish connections as (if you don't
+specify the host name in the service name when running gss-server, and
+it's running on a different machine from gss-client, make sure to
+specify the server's host name in the service name you specify to
+gss-client!) and msg is the message.  The command-line options have
+the following meanings:
+
+-port  The TCP port to which to connect.  Default is 4444.
+
+-mech  The OID of the GSS-API mechanism to use.
+
+-d     Tells the client to delegate credentials to the server.  For
+       the Kerberos GSS-API mechanism, this means that a forwardable
+       TGT will be sent to the server, which will put it in its
+       credential cache (you must have acquired your tickets with
+       "kinit -f" for this to work).
+
+-seq Tells the client to enforce ordered message delivery via
+    sequencing.  
+
+-noreplay Tells the client to disable the use of replay
+    detection.
+
+-nomutual Tells the client to disable the use of mutual authentication.
+
+-f     Tells the client that the "msg" argument is actually the name
+       of a file whose contents should be used as the message.
+
+-q     Tells the client to be quiet, i.e., to only print error
+       messages.
+
+-ccount        Specifies how many sessions the client should initiate with
+       the server (the "connection count").
+
+-mcount        Specifies how many times the message should be sent to the
+       server in each session (the "message count").
+
+-na    Tells the client not to do any authentication with the
+       server.  Implies "-nw", "-nx" and "-nm".
+
+-nw    Tells the client not to "wrap" messages.  Implies "-nx".
+
+-nx    Tells the client not to encrypt messages.
+
+-nm    Tells the client not to ask the server to send back a
+       cryptographic checksum ("MIC").
+
+To run the server on a host, you need to make sure that the principal
+corresponding to service_name is in the default keytab on the server
+host, and that the gss-server process can read the keytab.  For
+example, the service name "host@server" corresponds to the Kerberos
+principal "host/server.domain.com@REALM".
+
+This sample application uses the following GSS-API functions:
+
+       gss_accept_sec_context          gss_inquire_names_for_mech
+       gss_acquire_cred                gss_oid_to_str
+       gss_delete_sec_context          gss_release_buffer
+       gss_display_name                gss_release_cred
+       gss_display_status              gss_release_name
+       gss_export_sec_context          gss_release_oid
+       gss_get_mic                     gss_release_oid_set
+       gss_import_name                 gss_str_to_oid
+       gss_import_sec_context          gss_unwrap
+       gss_init_sec_context            gss_verify_mic
+       gss_inquire_context             gss_wrap
+  
+This application was originally written by Barry Jaspan of OpenVision
+Technologies, Inc.  It was updated significantly by Jonathan Kamens of
+OpenVision Technologies, Inc.
+
+$Id$
diff --git a/src/tests/gss-threads/gss-client.c b/src/tests/gss-threads/gss-client.c
new file mode 100644 (file)
index 0000000..f199dc2
--- /dev/null
@@ -0,0 +1,883 @@
+/*
+ * 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.
+ */
+/*
+ * Copyright (C) 2003, 2004 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ * 
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _WIN32
+#include <windows.h>
+#include <winsock.h>
+#else
+#include <unistd.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pthread.h>
+#endif
+
+#include <gssapi/gssapi_generic.h>
+#include "gss-misc.h"
+#include "port-sockets.h"
+#include "fake-addrinfo.h"
+
+static int verbose = 1;
+
+static void usage()
+{
+     fprintf(stderr, "Usage: gss-client [-port port] [-mech mechanism] [-d]\n");
+     fprintf(stderr, "       [-seq] [-noreplay] [-nomutual]");
+     fprintf(stderr, " [-threads num]");
+     fprintf(stderr, "\n");
+     fprintf(stderr, "       [-f] [-q] [-ccount count] [-mcount count]\n");
+     fprintf(stderr, "       [-v1] [-na] [-nw] [-nx] [-nm] host service msg\n");
+     exit(1);
+}
+
+/*
+ * Function: get_server_info
+ *
+ * Purpose: Sets up a socket address for the named host and port.
+ *
+ * Arguments:
+ *
+ *     host            (r) the target host name
+ *     port            (r) the target port, in host byte order
+ *
+ * Returns: 0 on success, or -1 on failure
+ *
+ * Effects:
+ *
+ * The host name is resolved with gethostbyname(), and "saddr" is set
+ * to the desired socket address.  If an error occurs, an error
+ * message is displayed and -1 is returned.
+ */
+struct sockaddr_in saddr;
+static int get_server_info(host, port)
+    char *host;
+    u_short port;
+{
+     struct hostent *hp;
+
+     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);
+     return 0;
+}
+
+/*
+ * Function: connect_to_server
+ *
+ * Purpose: Opens a TCP connection to the name host and port.
+ *
+ * Arguments:
+ *
+ *     host            (r) the target host name
+ *     port            (r) the target port, in host byte order
+ *
+ * Returns: the established socket file desciptor, or -1 on failure
+ *
+ * Effects:
+ *
+ * The host name is resolved with gethostbyname(), and the socket is
+ * opened and connected.  If an error occurs, an error message is
+ * displayed and -1 is returned.
+ */
+static int connect_to_server()
+{
+    int s;
+
+     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) closesocket(s);
+         return -1;
+     }
+     return s;
+}
+
+/*
+ * Function: client_establish_context
+ *
+ * Purpose: establishes a GSS-API context with a specified service and
+ * returns the context handle
+ *
+ * Arguments:
+ *
+ *     s                   (r) an established TCP connection to the service
+ *     service_name(r) the ASCII service name of the service
+ *     gss_flags       (r) GSS-API delegation flag (if any)
+ *     auth_flag       (r) whether to actually do authentication
+ *  v1_format   (r) whether the v1 sample protocol should be used
+ *     oid                 (r) OID of the mechanism to use
+ *     context         (w) the established GSS-API context
+ *     ret_flags       (w) the returned flags from init_sec_context
+ *
+ * Returns: 0 on success, -1 on failure
+ *
+ * Effects:
+ * 
+ * service_name is imported as a GSS-API name and a GSS-API context is
+ * established with the corresponding service; the service should be
+ * listening on the TCP connection s.  The default GSS-API mechanism
+ * is used, and mutual authentication and replay detection are
+ * requested.
+ * 
+ * If successful, the context handle is returned in context.  If
+ * unsuccessful, the GSS-API error messages are displayed on stderr
+ * and -1 is returned.
+ */
+static int client_establish_context(s, service_name, gss_flags, auth_flag,
+                                   v1_format, oid, gss_context, ret_flags)
+     int s;
+     char *service_name;
+     gss_OID oid;
+     OM_uint32 gss_flags;
+     int auth_flag;
+     int v1_format;
+     gss_ctx_id_t *gss_context;
+     OM_uint32 *ret_flags;
+{
+     if (auth_flag) {
+       gss_buffer_desc send_tok, recv_tok, *token_ptr;
+       gss_name_t target_name;
+       OM_uint32 maj_stat, min_stat, init_sec_min_stat;
+       int token_flags;
+
+       /*
+       * Import the name into target_name.  Use send_tok to save
+       * local variable space.
+       */
+       send_tok.value = service_name;
+       send_tok.length = strlen(service_name) ;
+       maj_stat = gss_import_name(&min_stat, &send_tok,
+                                 (gss_OID) gss_nt_service_name, &target_name);
+       if (maj_stat != GSS_S_COMPLETE) {
+        display_status("parsing name", maj_stat, min_stat);
+        return -1;
+       }
+     
+       if (!v1_format) {
+        if (send_token(s, TOKEN_NOOP|TOKEN_CONTEXT_NEXT, empty_token) < 0) {
+          (void) gss_release_name(&min_stat, &target_name);
+          return -1;
+        }
+       }
+
+       /*
+       * Perform the context-establishement loop.
+       *
+       * On each pass through the loop, token_ptr points to the token
+       * to send to the server (or GSS_C_NO_BUFFER on the first pass).
+       * Every generated token is stored in send_tok which is then
+       * transmitted to the server; every received token is stored in
+       * recv_tok, which token_ptr is then set to, to be processed by
+       * the next call to gss_init_sec_context.
+       * 
+       * GSS-API guarantees that send_tok's length will be non-zero
+       * if and only if the server is expecting another token from us,
+       * and that gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if
+       * and only if the server has another token to send us.
+       */
+     
+       token_ptr = GSS_C_NO_BUFFER;
+       *gss_context = GSS_C_NO_CONTEXT;
+
+       do {
+        maj_stat =
+          gss_init_sec_context(&init_sec_min_stat,
+                               GSS_C_NO_CREDENTIAL,
+                               gss_context,
+                               target_name,
+                               oid,
+                               gss_flags,
+                               0,
+                               NULL,   /* no channel bindings */
+                               token_ptr,
+                               NULL,   /* ignore mech type */
+                               &send_tok,
+                               ret_flags,
+                               NULL);  /* ignore time_rec */
+
+        if (token_ptr != GSS_C_NO_BUFFER)
+          free (recv_tok.value);
+
+        if (send_tok.length != 0) {
+          if (verbose)
+            printf("Sending init_sec_context token (size=%d)...",
+                   (int) send_tok.length);
+          if (send_token(s, v1_format?0:TOKEN_CONTEXT, &send_tok) < 0) {
+            (void) gss_release_buffer(&min_stat, &send_tok);
+            (void) gss_release_name(&min_stat, &target_name);
+            if (*gss_context != GSS_C_NO_CONTEXT) {
+                gss_delete_sec_context(&min_stat, gss_context,
+                                       GSS_C_NO_BUFFER);
+                *gss_context = GSS_C_NO_CONTEXT;
+            }
+            return -1;
+          }
+        }
+        (void) gss_release_buffer(&min_stat, &send_tok);
+        if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) {
+             display_status("initializing context", maj_stat,
+                            init_sec_min_stat);
+             (void) gss_release_name(&min_stat, &target_name);
+             if (*gss_context != GSS_C_NO_CONTEXT)
+                     gss_delete_sec_context(&min_stat, gss_context,
+                                            GSS_C_NO_BUFFER);
+             return -1;
+        }
+         
+        if (maj_stat == GSS_S_CONTINUE_NEEDED) {
+          if (verbose)
+            printf("continue needed...");
+          if (recv_token(s, &token_flags, &recv_tok) < 0) {
+            (void) gss_release_name(&min_stat, &target_name);
+            return -1;
+          }
+          token_ptr = &recv_tok;
+        }
+        if (verbose)
+          printf("\n");
+       } while (maj_stat == GSS_S_CONTINUE_NEEDED);
+
+       (void) gss_release_name(&min_stat, &target_name);
+     }
+     else {
+       if (send_token(s, TOKEN_NOOP, empty_token) < 0)
+        return -1;
+     }
+
+     return 0;
+}
+
+static void read_file(file_name, in_buf)
+    char               *file_name;
+    gss_buffer_t       in_buf;
+{
+    int fd, 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(2);
+    }
+    if (fstat(fd, &stat_buf) < 0) {
+       perror("fstat");
+       exit(3);
+    }
+    in_buf->length = stat_buf.st_size;
+
+    if (in_buf->length == 0) {
+       in_buf->value = NULL;
+       return;
+    }
+
+    if ((in_buf->value = malloc(in_buf->length)) == 0) {
+       fprintf(stderr, "Couldn't allocate %d byte buffer for reading file\n",
+               (int) in_buf->length);
+       exit(4);
+    }
+
+    /* this code used to check for incomplete reads, but you can't get
+       an incomplete read on any file for which fstat() is meaningful */
+
+    count = read(fd, in_buf->value, in_buf->length);
+    if (count < 0) {
+       perror("read");
+       exit(5);
+    }
+    if (count < in_buf->length)
+       fprintf(stderr, "Warning, only read in %d bytes, expected %d\n",
+               count, (int) in_buf->length);
+}
+
+/*
+ * Function: call_server
+ *
+ * Purpose: Call the "sign" service.
+ *
+ * Arguments:
+ *
+ *     host            (r) the host providing the service
+ *     port            (r) the port to connect to on host
+ *     service_name    (r) the GSS-API service name to authenticate to
+ *     gss_flags       (r) GSS-API delegation flag (if any)
+ *     auth_flag       (r) whether to do authentication
+ *     wrap_flag       (r) whether to do message wrapping at all
+ *     encrypt_flag    (r) whether to do encryption while wrapping
+ *     mic_flag        (r) whether to request a MIC from the server
+ *     msg             (r) the message to have "signed"
+ *     use_file        (r) whether to treat msg as an input file name
+ *     mcount          (r) the number of times to send the message
+ *
+ * Returns: 0 on success, -1 on failure
+ *
+ * Effects:
+ * 
+ * call_server opens a TCP connection to <host:port> and establishes a
+ * GSS-API context with service_name over the connection.  It then
+ * seals msg in a GSS-API token with gss_wrap, sends it to the server,
+ * reads back a GSS-API signature block for msg from the server, and
+ * verifies it with gss_verify.  -1 is returned if any step fails,
+ * otherwise 0 is returned.  */
+static int call_server(host, port, oid, service_name, gss_flags, auth_flag,
+                      wrap_flag, encrypt_flag, mic_flag, v1_format, msg, use_file,
+                      mcount)
+     char *host;
+     u_short port;
+     gss_OID oid;
+     char *service_name;
+     OM_uint32 gss_flags;
+     int auth_flag, wrap_flag, encrypt_flag, mic_flag;
+     int v1_format;
+     char *msg;
+     int use_file;
+     int mcount;
+{
+     gss_ctx_id_t context;
+     gss_buffer_desc in_buf, out_buf;
+     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;
+     size_t    i;
+     int token_flags;
+
+     /* Open connection */
+     if ((s = connect_to_server()) < 0)
+         return -1;
+
+     /* Establish context */
+     if (client_establish_context(s, service_name, gss_flags, auth_flag,
+                                 v1_format, oid, &context,
+                                 &ret_flags) < 0) {
+         (void) closesocket(s);
+         return -1;
+     }
+
+     if (auth_flag && verbose) {
+         /* display the flags */
+         display_ctx_flags(ret_flags);
+
+         /* Get context information */
+         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;
+         }
+
+         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;
+         }
+         printf("\"%.*s\" to \"%.*s\", lifetime %d, 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;
+         }
+         printf("Name type of source name is %.*s.\n",
+                 (int) oid_name.length, (char *) oid_name.value);
+         (void) gss_release_buffer(&min_stat, &oid_name);
+
+         /* Now get the names supported by the mechanism */
+         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;
+         }
+         printf("Mechanism %.*s supports %d names\n",
+                 (int) oid_name.length, (char *) oid_name.value,
+                 (int) 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;
+             }
+             printf("  %d: %.*s\n", (int) 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 {
+        /* Seal the message */
+        in_buf.value = msg;
+        in_buf.length = strlen(msg);
+    }
+
+    for (i = 0; i < mcount; i++) {
+        if (wrap_flag) {
+            maj_stat = gss_wrap(&min_stat, context, encrypt_flag, GSS_C_QOP_DEFAULT,
+                                 &in_buf, &state, &out_buf);
+            if (maj_stat != GSS_S_COMPLETE) {
+                display_status("wrapping message", maj_stat, min_stat);
+                (void) closesocket(s);
+                (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER);
+                return -1;
+            } else if (encrypt_flag && ! state) {
+                fprintf(stderr, "Warning!  Message not encrypted.\n");
+            }
+        }
+        else {
+            out_buf = in_buf;
+        }
+
+        /* Send to server */
+        if (send_token(s, (v1_format?0
+                            :(TOKEN_DATA |
+                               (wrap_flag ? TOKEN_WRAPPED : 0) |
+                               (encrypt_flag ? TOKEN_ENCRYPTED : 0) |
+                               (mic_flag ? TOKEN_SEND_MIC : 0))), &out_buf) < 0) {
+            (void) closesocket(s);
+            (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER);
+            return -1;
+        }
+        if (out_buf.value != in_buf.value)
+            (void) gss_release_buffer(&min_stat, &out_buf);
+
+        /* Read signature block into out_buf */
+        if (recv_token(s, &token_flags, &out_buf) < 0) {
+            (void) closesocket(s);
+            (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER);
+            return -1;
+        }
+
+        if (mic_flag) {
+            /* Verify signature block */
+            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) closesocket(s);
+                (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER);
+                return -1;
+            }
+
+            if (verbose)
+                printf("Signature verified.\n");
+        }
+        else {
+            if (verbose)
+                printf("Response received.\n");
+        }
+
+        free (out_buf.value);
+    }
+
+    if (use_file)
+        free(in_buf.value);
+
+    /* Send NOOP */
+    if (!v1_format)
+        (void) send_token(s, TOKEN_NOOP, empty_token);
+
+    if (auth_flag) {
+        /* Delete context */
+        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) closesocket(s);
+            (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER);
+            return -1;
+        }
+
+        (void) gss_release_buffer(&min_stat, &out_buf);
+    }
+
+    (void) closesocket(s);
+    return 0;
+}
+
+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((int) mechanism[0])) {
+       mechstr = malloc(strlen(mechanism)+5);
+       if (!mechstr) {
+           fprintf(stderr, "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);
+}
+
+static int max_threads = 1;
+
+#ifdef _WIN32
+static thread_count = 0;
+static HANDLE hMutex = NULL;
+static HANDLE hEvent = NULL;
+
+void
+InitHandles(void)
+{
+    hMutex = CreateMutex(NULL, FALSE, NULL);
+    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+}
+
+void
+CleanupHandles(void)
+{
+    CloseHandle(hMutex);
+    CloseHandle(hEvent);
+}
+
+BOOL
+WaitAndIncrementThreadCounter(void)
+{
+    for (;;) {
+        if (WaitForSingleObject(hMutex, INFINITE) == WAIT_OBJECT_0) {
+            if ( thread_count < max_threads ) {
+                thread_count++;
+                ReleaseMutex(hMutex);
+                return TRUE;
+            } else {
+                ReleaseMutex(hMutex);
+
+                if (WaitForSingleObject(hEvent, INFINITE) == WAIT_OBJECT_0) {
+                    continue;
+                } else {
+                    return FALSE;
+                }
+            }
+        } else {
+            return FALSE;
+        }
+    }
+}
+
+BOOL
+DecrementAndSignalThreadCounter(void)
+{
+    if (WaitForSingleObject(hMutex, INFINITE) == WAIT_OBJECT_0) {
+        if (thread_count == max_threads)
+            SetEvent(hEvent);
+        thread_count--;
+        ReleaseMutex(hMutex);
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+#else /* assume pthread */
+static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t counter_cond = PTHREAD_COND_INITIALIZER;
+int counter = 0;
+
+static int
+WaitAndIncrementThreadCounter(void)
+{
+    int err;
+    err = pthread_mutex_lock(&counter_mutex);
+    if (err) {
+       perror("pthread_mutex_lock");
+       return 0;
+    }
+    if (counter == max_threads) {
+       err = pthread_cond_wait(&counter_cond, &counter_mutex);
+       if (err) {
+           perror("pthread_cond_wait");
+           return 0;
+       }
+    }
+    counter++;
+    pthread_mutex_unlock(&counter_mutex);
+    return 1;
+}
+static void
+DecrementAndSignalThreadCounter(void)
+{
+    int err;
+    sleep(1);
+    err = pthread_mutex_lock(&counter_mutex);
+    if (err) {
+       perror("pthread_mutex_lock");
+       return;
+    }
+    if (counter == max_threads)
+       pthread_cond_broadcast(&counter_cond);
+    counter--;
+    pthread_mutex_unlock(&counter_mutex);
+}
+#endif
+
+static char *service_name, *server_host, *msg;
+static char *mechanism = 0;
+static u_short port = 4444;
+static int use_file = 0;
+static OM_uint32 gss_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;
+static OM_uint32 min_stat;
+static gss_OID oid = GSS_C_NULL_OID;
+static int mcount = 1, ccount = 1;
+static int auth_flag, wrap_flag, encrypt_flag, mic_flag, v1_format;
+
+static void worker_bee(void * unused)
+{
+    printf("worker bee!\n");
+    if (call_server(server_host, port, oid, service_name,
+                    gss_flags, auth_flag, wrap_flag, encrypt_flag, mic_flag,
+                    v1_format, msg, use_file, mcount) < 0)
+        if ( max_threads == 1 )
+            exit(6);
+
+    if ( max_threads > 1 )
+        DecrementAndSignalThreadCounter();
+    free(unused);
+}
+
+int main(argc, argv)
+     int argc;
+     char **argv;
+{
+     int i;
+
+     display_file = stdout;
+     auth_flag = wrap_flag = encrypt_flag = mic_flag = 1;
+     v1_format = 0;
+
+     /* Parse arguments. */
+     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;
+          } 
+#if defined(_WIN32) || 1
+           else if (strcmp(*argv, "-threads") == 0) {
+               argc--; argv++;
+               if (!argc) usage();
+               max_threads = atoi(*argv);
+           } 
+#endif
+           else if (strcmp(*argv, "-d") == 0) {
+              gss_flags |= GSS_C_DELEG_FLAG;
+          } else if (strcmp(*argv, "-seq") == 0) {
+              gss_flags |= GSS_C_SEQUENCE_FLAG;
+          } else if (strcmp(*argv, "-noreplay") == 0) {
+              gss_flags &= ~GSS_C_REPLAY_FLAG;
+          } else if (strcmp(*argv, "-nomutual") == 0) {
+              gss_flags &= ~GSS_C_MUTUAL_FLAG;
+         } else if (strcmp(*argv, "-f") == 0) {
+              use_file = 1;
+         } else if (strcmp(*argv, "-q") == 0) {
+              verbose = 0;
+         } else if (strcmp(*argv, "-ccount") == 0) {
+           argc--; argv++;
+           if (!argc) usage();
+           ccount = atoi(*argv);
+           if (ccount <= 0) usage();
+         } else if (strcmp(*argv, "-mcount") == 0) {
+           argc--; argv++;
+           if (!argc) usage();
+           mcount = atoi(*argv);
+           if (mcount < 0) usage();
+         } else if (strcmp(*argv, "-na") == 0) {
+           auth_flag = wrap_flag = encrypt_flag = mic_flag = 0;
+         } else if (strcmp(*argv, "-nw") == 0) {
+           wrap_flag = 0;
+         } else if (strcmp(*argv, "-nx") == 0) {
+           encrypt_flag = 0;
+         } else if (strcmp(*argv, "-nm") == 0) {
+           mic_flag = 0;
+         } else  if (strcmp(*argv, "-v1") == 0) {
+           v1_format = 1;
+         } else
+           break;
+         argc--; argv++;
+     }
+     if (argc != 3)
+         usage();
+
+#ifdef _WIN32
+    if (max_threads < 1) {
+        fprintf(stderr, "warning: there must be at least one thread\n");
+        max_threads = 1;
+    }
+
+    InitHandles();
+    SetEnvironmentVariable("KERBEROSLOGIN_NEVER_PROMPT","1");
+#endif
+
+     server_host = *argv++;
+     service_name = *argv++;
+     msg = *argv++;
+
+     if (mechanism)
+        parse_oid(mechanism, &oid);
+
+     if (get_server_info(server_host, port) < 0) {
+        exit(1);
+     }
+
+    if ( max_threads == 1 ) {
+        for (i = 0; i < ccount; i++) {
+            worker_bee(0);
+        }
+    } else {
+        for (i = 0; i < ccount; i++) {
+            if ( WaitAndIncrementThreadCounter() ) {
+#ifdef _WIN32
+                uintptr_t handle = _beginthread(worker_bee, 0, (void *)0);
+                if (handle == (uintptr_t)-1) {
+                    exit(7);
+                }
+#else
+               int err;
+               pthread_t thr;
+               err = pthread_create(&thr, 0, (void *(*)(void *))worker_bee, malloc(12));
+               if (err) {
+                   perror("pthread_create");
+                   exit(7);
+               }
+               (void) pthread_detach(thr);
+#endif
+            } else {
+                exit(8);
+            }
+        }
+    }
+
+    if (oid != GSS_C_NULL_OID)
+        (void) gss_release_oid(&min_stat, &oid);
+        
+#ifdef _WIN32
+    CleanupHandles();
+#else
+    if (max_threads > 1)
+       sleep(10);
+#endif
+
+    return 0;
+}
diff --git a/src/tests/gss-threads/gss-misc.c b/src/tests/gss-threads/gss-misc.c
new file mode 100644 (file)
index 0000000..c912792
--- /dev/null
@@ -0,0 +1,423 @@
+/*
+ * 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.
+ */
+/*
+ * Copyright (C) 2003, 2004 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ * 
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#if !defined(lint) && !defined(__CODECENTER__)
+static char *rcsid = "$Header$";
+#endif
+
+#include <stdio.h>
+#ifdef _WIN32
+#include <windows.h>
+#include <winsock.h>
+#else
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#endif
+#include <errno.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <string.h>
+
+/* need struct timeval */
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+
+#include <gssapi/gssapi_generic.h>
+#include "gss-misc.h"
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#else
+extern char *malloc();
+#endif
+
+FILE *display_file;
+
+gss_buffer_desc empty_token_buf = { 0, (void *) "" };
+gss_buffer_t empty_token = &empty_token_buf;
+
+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 = send(fildes, ptr, nbyte, 0);
+         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;
+    fd_set rfds;
+    struct timeval tv;
+
+    FD_ZERO(&rfds);
+    FD_SET(fildes, &rfds);
+    tv.tv_sec = 10;
+    tv.tv_usec = 0;
+
+    for (ptr = buf; nbyte; ptr += ret, nbyte -= ret) {
+       if (select(FD_SETSIZE, &rfds, NULL, NULL, &tv) <= 0
+           || !FD_ISSET(fildes, &rfds))
+           return(ptr-buf);
+       ret = recv(fildes, ptr, nbyte, 0);
+       if (ret < 0) {
+           if (errno == EINTR)
+               continue;
+           return(ret);
+       } else if (ret == 0) {
+           return(ptr-buf);
+       }
+    }
+
+    return(ptr-buf);
+}
+
+/*
+ * Function: send_token
+ *
+ * Purpose: Writes a token to a file descriptor.
+ *
+ * Arguments:
+ *
+ *     s               (r) an open file descriptor
+ *     flags           (r) the flags to write
+ *     tok             (r) the token to write
+ *
+ * Returns: 0 on success, -1 on failure
+ *
+ * Effects:
+ *
+ * If the flags are non-null, send_token writes the token flags (a
+ * single byte, even though they're passed in in an integer). Next,
+ * the token length (as a network long) and then the token data are
+ * written to the file descriptor s.  It returns 0 on success, and -1
+ * if an error occurs or if it could not write all the data.
+ */
+int send_token(s, flags, tok)
+     int s;
+     int flags;
+     gss_buffer_t tok;
+{
+     int ret;
+     unsigned char char_flags = (unsigned char) flags;
+     unsigned char lenbuf[4];
+
+     if (char_flags) {
+        ret = write_all(s, (char *)&char_flags, 1);
+        if (ret != 1) {
+            perror("sending token flags");
+            return -1;
+        }
+     }
+     if (tok->length > 0xffffffffUL)
+        abort();
+     lenbuf[0] = (tok->length >> 24) & 0xff;
+     lenbuf[1] = (tok->length >> 16) & 0xff;
+     lenbuf[2] = (tok->length >> 8) & 0xff;
+     lenbuf[3] = tok->length & 0xff;
+
+     ret = write_all(s, lenbuf, 4);
+     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, 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, (int) tok->length);
+        return -1;
+     }
+     
+     return 0;
+}
+
+/*
+ * Function: recv_token
+ *
+ * Purpose: Reads a token from a file descriptor.
+ *
+ * Arguments:
+ *
+ *     s               (r) an open file descriptor
+ *     flags           (w) the read flags
+ *     tok             (w) the read token
+ *
+ * Returns: 0 on success, -1 on failure
+ *
+ * Effects:
+ * 
+ * recv_token reads the token flags (a single byte, even though
+ * they're stored into an integer, then reads the token length (as a
+ * network long), allocates memory to hold the data, and then reads
+ * the token data from the file descriptor s.  It blocks to read the
+ * length and data, if necessary.  On a successful return, the token
+ * should be freed with gss_release_buffer.  It returns 0 on success,
+ * and -1 if an error occurs or if it could not read all the data.
+ */
+int recv_token(s, flags, tok)
+     int s;
+     int *flags;
+     gss_buffer_t tok;
+{
+     int ret;
+     unsigned char char_flags;
+     unsigned char lenbuf[4];
+
+     ret = read_all(s, (char *) &char_flags, 1);
+     if (ret < 0) {
+       perror("reading token flags");
+       return -1;
+     } else if (! ret) {
+       if (display_file)
+        fputs("reading token flags: 0 bytes read\n", display_file);
+       return -1;
+     } else {
+       *flags = (int) char_flags;
+     }
+
+     if (char_flags == 0 ) {
+     lenbuf[0] = 0;
+     ret = read_all(s, &lenbuf[1], 3);
+     if (ret < 0) {
+        perror("reading token length");
+        return -1;
+     } else if (ret != 3) {
+        if (display_file)
+            fprintf(display_file, 
+                    "reading token length: %d of %d bytes read\n", 
+                    ret, 3);
+        return -1;
+     }
+     }
+     else {
+       ret = read_all(s, lenbuf, 4);
+       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 = ((lenbuf[0] << 24)
+                   | (lenbuf[1] << 16)
+                   | (lenbuf[2] << 8)
+                   | lenbuf[3]);
+     tok->value = (char *) malloc(tok->length ? tok->length : 1);
+     if (tok->length && 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, 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, (int) tok->length);
+         free(tok->value);
+         return -1;
+     }
+
+     return 0;
+}
+
+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;
+     OM_uint32 msg_ctx;
+     
+     msg_ctx = 0;
+     while (1) {
+         maj_stat = gss_display_status(&min_stat, code,
+                                      type, GSS_C_NULL_OID,
+                                      &msg_ctx, &msg);
+         if (display_file)
+             fprintf(display_file, "GSS-API error %s: %s\n", m,
+                     (char *)msg.value); 
+         (void) gss_release_buffer(&min_stat, &msg);
+         
+         if (!msg_ctx)
+              break;
+     }
+}
+
+/*
+ * Function: display_status
+ *
+ * Purpose: displays GSS-API messages
+ *
+ * Arguments:
+ *
+ *     msg             a string to be displayed with the message
+ *     maj_stat        the GSS-API major status code
+ *     min_stat        the GSS-API minor status code
+ *
+ * Effects:
+ *
+ * The GSS-API messages associated with maj_stat and min_stat are
+ * displayed on stderr, each preceeded by "GSS-API error <msg>: " and
+ * followed by a newline.
+ */
+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);
+}
+
+/*
+ * Function: display_ctx_flags
+ *
+ * Purpose: displays the flags returned by context initation in
+ *         a human-readable form
+ *
+ * Arguments:
+ *
+ *     int             ret_flags
+ *
+ * Effects:
+ *
+ * Strings corresponding to the context flags are printed on
+ * stdout, preceded by "context flag: " and followed by a newline
+ */
+
+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);
+}
+
+#ifdef _WIN32
+#include <sys\timeb.h>
+#include <time.h>
+
+int gettimeofday (struct timeval *tv, void *ignore_tz)
+{
+    struct _timeb tb;
+    _tzset();
+    _ftime(&tb);
+    if (tv) {
+       tv->tv_sec = tb.time;
+       tv->tv_usec = tb.millitm * 1000;
+    }
+    return 0;
+}
+#endif /* _WIN32 */
diff --git a/src/tests/gss-threads/gss-misc.h b/src/tests/gss-threads/gss-misc.h
new file mode 100644 (file)
index 0000000..35b3b73
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef _GSSMISC_H_
+#define _GSSMISC_H_
+
+#include <gssapi/gssapi_generic.h>
+#include <stdio.h>
+
+extern FILE *display_file;
+
+int send_token
+       (int s, int flags, gss_buffer_t tok);
+int recv_token
+       (int s, int *flags, gss_buffer_t tok);
+void display_status
+       (char *msg, OM_uint32 maj_stat, OM_uint32 min_stat);
+void display_ctx_flags
+       (OM_uint32 flags);
+void print_token
+       (gss_buffer_t tok);
+
+/* Token types */
+#define TOKEN_NOOP             (1<<0)
+#define TOKEN_CONTEXT          (1<<1)
+#define TOKEN_DATA             (1<<2)
+#define TOKEN_MIC              (1<<3)
+
+/* Token flags */
+#define TOKEN_CONTEXT_NEXT     (1<<4)
+#define TOKEN_WRAPPED          (1<<5)
+#define TOKEN_ENCRYPTED                (1<<6)
+#define TOKEN_SEND_MIC         (1<<7)
+
+extern gss_buffer_t empty_token;
+
+#endif
diff --git a/src/tests/gss-threads/gss-server.c b/src/tests/gss-threads/gss-server.c
new file mode 100644 (file)
index 0000000..6cbac84
--- /dev/null
@@ -0,0 +1,850 @@
+/*
+ * 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.
+ */
+/*
+ * Copyright (C) 2004 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ * 
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include <stdio.h>
+#ifdef _WIN32
+#include <windows.h>
+#include <winsock.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <signal.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <gssapi/gssapi_generic.h>
+#include "gss-misc.h"
+#include "port-sockets.h"
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+static void usage()
+{
+     fprintf(stderr, "Usage: gss-server [-port port] [-verbose] [-once]");
+#ifdef _WIN32
+     fprintf(stderr, " [-threads num]");
+#endif
+     fprintf(stderr, "\n");
+     fprintf(stderr, "       [-inetd] [-export] [-logfile file] service_name\n");
+     exit(1);
+}
+
+FILE *log;
+
+int verbose = 0;
+
+/*
+ * Function: server_acquire_creds
+ *
+ * Purpose: imports a service name and acquires credentials for it
+ *
+ * Arguments:
+ *
+ *     service_name    (r) the ASCII service name
+ *     server_creds    (w) the GSS-API service credentials
+ *
+ * Returns: 0 on success, -1 on failure
+ *
+ * Effects:
+ *
+ * The service name is imported with gss_import_name, and service
+ * credentials are acquired with gss_acquire_cred.  If either opertion
+ * fails, an error message is displayed and -1 is returned; otherwise,
+ * 0 is returned.
+ */
+static int server_acquire_creds(service_name, server_creds)
+     char *service_name;
+     gss_cred_id_t *server_creds;
+{
+     gss_buffer_desc name_buf;
+     gss_name_t server_name;
+     OM_uint32 maj_stat, min_stat;
+
+     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_nt_service_name, &server_name);
+     if (maj_stat != GSS_S_COMPLETE) {
+         display_status("importing name", maj_stat, min_stat);
+         return -1;
+     }
+
+     maj_stat = gss_acquire_cred(&min_stat, server_name, 0,
+                                GSS_C_NULL_OID_SET, 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;
+}
+
+/*
+ * Function: server_establish_context
+ *
+ * Purpose: establishses a GSS-API context as a specified service with
+ * an incoming client, and returns the context handle and associated
+ * client name
+ *
+ * Arguments:
+ *
+ *     s               (r) an established TCP connection to the client
+ *     service_creds   (r) server credentials, from gss_acquire_cred
+ *     context         (w) the established GSS-API context
+ *     client_name     (w) the client's ASCII name
+ *
+ * Returns: 0 on success, -1 on failure
+ *
+ * Effects:
+ *
+ * Any valid client request is accepted.  If a context is established,
+ * its handle is returned in context and the client name is returned
+ * in client_name and 0 is returned.  If unsuccessful, an error
+ * message is displayed and -1 is returned.
+ */
+static 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, acc_sec_min_stat;
+     gss_buffer_desc   oid_name;
+     int token_flags;
+
+     if (recv_token(s, &token_flags, &recv_tok) < 0)
+       return -1;
+
+     if (recv_tok.value) {
+       free (recv_tok.value);
+       recv_tok.value = NULL;
+     }
+
+     if (! (token_flags & TOKEN_NOOP)) {
+       if (log)
+        fprintf(log, "Expected NOOP token, got %d token instead\n",
+                token_flags);
+       return -1;
+     }
+
+     *context = GSS_C_NO_CONTEXT;
+
+     if (token_flags & TOKEN_CONTEXT_NEXT) {
+       do {
+        if (recv_token(s, &token_flags, &recv_tok) < 0)
+          return -1;
+
+        if (verbose && log) {
+          fprintf(log, "Received token (size=%d): \n", (int) recv_tok.length);
+          print_token(&recv_tok);
+        }
+
+        maj_stat =
+          gss_accept_sec_context(&acc_sec_min_stat,
+                                 context,
+                                 server_creds,
+                                 &recv_tok,
+                                 GSS_C_NO_CHANNEL_BINDINGS,
+                                 &client,
+                                 &doid,
+                                 &send_tok,
+                                 ret_flags,
+                                 NULL,         /* ignore time_rec */
+                                 NULL);        /* ignore del_cred_handle */
+
+        if(recv_tok.value) {
+            free(recv_tok.value);
+            recv_tok.value = NULL;
+        }
+
+        if (send_tok.length != 0) {
+          if (verbose && log) {
+            fprintf(log,
+                    "Sending accept_sec_context token (size=%d):\n",
+                    (int) send_tok.length);
+            print_token(&send_tok);
+          }
+          if (send_token(s, TOKEN_CONTEXT, &send_tok) < 0) {
+            if (log)
+              fprintf(log, "failure sending token\n");
+            return -1;
+          }
+
+          (void) gss_release_buffer(&min_stat, &send_tok);
+        }
+        if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) {
+             display_status("accepting context", maj_stat,
+                            acc_sec_min_stat);
+             if (*context != GSS_C_NO_CONTEXT)
+                     gss_delete_sec_context(&min_stat, context,
+                                            GSS_C_NO_BUFFER);
+             return -1;
+        }
+        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 the flags */
+       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;
+        }
+        fprintf(log, "Accepted connection using mechanism OID %.*s.\n",
+                (int) oid_name.length, (char *) oid_name.value);
+        (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;
+       }
+       maj_stat = gss_release_name(&min_stat, &client);
+       if (maj_stat != GSS_S_COMPLETE) {
+        display_status("releasing name", maj_stat, min_stat);
+        return -1;
+       }
+     }
+     else {
+       client_name->length = *ret_flags = 0;
+
+       if (log)
+        fprintf(log, "Accepted unauthenticated connection.\n");
+     }
+
+     return 0;
+}
+
+/*
+ * Function: create_socket
+ *
+ * Purpose: Opens a listening TCP socket.
+ *
+ * Arguments:
+ *
+ *     port            (r) the port number on which to listen
+ *
+ * Returns: the listening socket file descriptor, or -1 on failure
+ *
+ * Effects:
+ *
+ * A listening socket on the specified port and created and returned.
+ * On error, an error message is displayed and -1 is returned.
+ */
+static 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;
+     }
+     /* Let the socket be reused right away */
+     (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;
+}
+
+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);
+}
+
+/*
+ * Yes, yes, this isn't the best place for doing this test.
+ * DO NOT REMOVE THIS UNTIL A BETTER TEST HAS BEEN WRITTEN, THOUGH.
+ *                                     -TYT
+ */
+static 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;
+       
+       /*
+        * Attempt to save and then restore the context.
+        */
+       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",
+                       (int) 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) {
+         if (log)
+           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;
+       }
+       free(copied_token.value);
+       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;
+}
+
+/*
+ * Function: sign_server
+ *
+ * Purpose: Performs the "sign" service.
+ *
+ * Arguments:
+ *
+ *     s               (r) a TCP socket on which a connection has been
+ *                     accept()ed
+ *     service_name    (r) the ASCII name of the GSS-API service to
+ *                     establish a context as
+ *     export          (r) whether to test context exporting
+ * 
+ * Returns: -1 on error
+ *
+ * Effects:
+ *
+ * sign_server establishes a context, and performs a single sign request.
+ *
+ * A sign request is a single GSS-API sealed token.  The token is
+ * unsealed and a signature block, produced with gss_sign, is returned
+ * to the sender.  The context is the destroyed and the connection
+ * closed.
+ *
+ * If any error occurs, -1 is returned.
+ */
+static int sign_server(s, server_creds, export)
+     int s;
+     gss_cred_id_t server_creds;
+     int export;
+{
+    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;
+    int token_flags;
+
+    /* Establish a context with the client */
+    if (server_establish_context(s, server_creds, &context,
+                                  &client_name, &ret_flags) < 0)
+        return(-1);
+
+    if (context == GSS_C_NO_CONTEXT) {
+        printf("Accepted unauthenticated connection.\n");
+    }
+    else {
+        printf("Accepted connection: \"%.*s\"\n",
+                (int) client_name.length, (char *) client_name.value);
+        (void) gss_release_buffer(&min_stat, &client_name);
+
+        if (export) {
+            for (i=0; i < 3; i++)
+                if (test_import_export_context(&context))
+                    return -1;
+        }
+    }
+
+    do {
+        /* Receive the message token */
+        if (recv_token(s, &token_flags, &xmit_buf) < 0)
+            return(-1);
+
+        if (token_flags & TOKEN_NOOP) {
+            if (log)
+                fprintf(log, "NOOP token\n");
+            if(xmit_buf.value) {
+                free(xmit_buf.value);
+                xmit_buf.value = 0;
+            }
+            break;
+        }
+
+        if (verbose && log) {
+            fprintf(log, "Message token (flags=%d):\n", token_flags);
+            print_token(&xmit_buf);
+        }
+
+        if ((context == GSS_C_NO_CONTEXT) &&
+             (    token_flags & (TOKEN_WRAPPED|TOKEN_ENCRYPTED|TOKEN_SEND_MIC))) {
+            if (log)
+                fprintf(log,
+                         "Unauthenticated client requested authenticated services!\n");
+            if(xmit_buf.value) {
+                free (xmit_buf.value);
+                xmit_buf.value = 0;
+            }
+            return(-1);
+        }
+
+        if (token_flags & TOKEN_WRAPPED) {
+            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("unsealing message", maj_stat, min_stat);
+                if(xmit_buf.value) {
+                    free (xmit_buf.value);
+                    xmit_buf.value = 0;
+                }
+                return(-1);
+            } else if (! conf_state && (token_flags & TOKEN_ENCRYPTED)) {
+                fprintf(stderr, "Warning!  Message not encrypted.\n");
+            }
+
+            if(xmit_buf.value) {
+                free (xmit_buf.value);
+                xmit_buf.value = 0;
+            }
+        }
+        else {
+            msg_buf = xmit_buf;
+        }
+
+        if (log) {
+            fprintf(log, "Received message: ");
+            cp = msg_buf.value;
+            if ((isprint((int) cp[0]) || isspace((int) cp[0])) &&
+                 (isprint((int) cp[1]) || isspace((int) cp[1]))) {
+                fprintf(log, "\"%.*s\"\n", (int) msg_buf.length, 
+                         (char *) msg_buf.value);
+                 } else {
+                     fprintf(log, "\n");
+                     print_token(&msg_buf);
+                 }
+        }
+
+        if (token_flags & TOKEN_SEND_MIC) {
+            /* Produce a signature block for the message */
+            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);
+            }
+
+            if(msg_buf.value) {
+                free (msg_buf.value);
+                msg_buf.value = 0;
+            }
+
+            /* Send the signature block to the client */
+            if (send_token(s, TOKEN_MIC, &xmit_buf) < 0)
+                return(-1);
+
+            if(xmit_buf.value) {
+                free (xmit_buf.value);
+                xmit_buf.value = 0;
+            }
+        }
+        else {
+            if(msg_buf.value) {
+                free (msg_buf.value);
+                msg_buf.value = 0;
+            }
+            if (send_token(s, TOKEN_NOOP, empty_token) < 0)
+                return(-1);
+        }
+    } while (1 /* loop will break if NOOP received */);
+
+    if (context != GSS_C_NO_CONTEXT) {
+        /* Delete context */
+        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);
+        }
+    }
+
+    if (log)
+        fflush(log);
+
+    return(0);
+}
+
+static int max_threads = 1;
+
+#ifdef _WIN32
+static thread_count = 0;
+static HANDLE hMutex = NULL;
+static HANDLE hEvent = NULL;
+
+void
+InitHandles(void)
+{
+    hMutex = CreateMutex(NULL, FALSE, NULL);
+    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+}
+
+void
+CleanupHandles(void)
+{
+    CloseHandle(hMutex);
+    CloseHandle(hEvent);
+}
+
+BOOL
+WaitAndIncrementThreadCounter(void)
+{
+    for (;;) {
+        if (WaitForSingleObject(hMutex, INFINITE) == WAIT_OBJECT_0) {
+            if ( thread_count < max_threads ) {
+                thread_count++;
+                ReleaseMutex(hMutex);
+                return TRUE;
+            } else {
+                ReleaseMutex(hMutex);
+
+                if (WaitForSingleObject(hEvent, INFINITE) == WAIT_OBJECT_0) {
+                    continue;
+                } else {
+                    return FALSE;
+                }
+            }
+        } else {
+            return FALSE;
+        }
+    }
+}
+
+BOOL
+DecrementAndSignalThreadCounter(void)
+{
+    if (WaitForSingleObject(hMutex, INFINITE) == WAIT_OBJECT_0) {
+        if ( thread_count == max_threads )
+            SetEvent(hEvent);
+        thread_count--;
+        ReleaseMutex(hMutex);
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+#else /* assume pthread */
+static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t counter_cond = PTHREAD_COND_INITIALIZER;
+int counter = 0;
+
+static int
+WaitAndIncrementThreadCounter(void)
+{
+    int err;
+    err = pthread_mutex_lock(&counter_mutex);
+    if (err) {
+       perror("pthread_mutex_lock");
+       return 0;
+    }
+    if (counter == max_threads) {
+       err = pthread_cond_wait(&counter_cond, &counter_mutex);
+       if (err) {
+           perror("pthread_cond_wait");
+           return 0;
+       }
+    }
+    counter++;
+    pthread_mutex_unlock(&counter_mutex);
+    return 1;
+}
+static void
+DecrementAndSignalThreadCounter(void)
+{
+    int err;
+    err = pthread_mutex_lock(&counter_mutex);
+    if (err) {
+       perror("pthread_mutex_lock");
+       return;
+    }
+    if (counter == max_threads)
+       pthread_cond_broadcast(&counter_cond);
+    counter--;
+    pthread_mutex_unlock(&counter_mutex);
+}
+#endif
+
+struct _work_plan {
+    int             s;
+    gss_cred_id_t   server_creds;
+    int             export;
+};
+
+static void *
+worker_bee(void * param)
+{
+    struct _work_plan *work = (struct _work_plan *) param;
+
+    /* this return value is not checked, because there's
+     * not really anything to do if it fails 
+     */
+    sign_server(work->s, work->server_creds, work->export);
+    closesocket(work->s);
+    free(work);
+
+#if defined _WIN32 || 1
+    if ( max_threads > 1 )
+        DecrementAndSignalThreadCounter();
+#endif
+    return 0;
+}
+
+int
+main(argc, argv)
+     int argc;
+     char **argv;
+{
+     char *service_name;
+     gss_cred_id_t server_creds;
+     OM_uint32 min_stat;
+     u_short port = 4444;
+     int once = 0;
+     int do_inetd = 0;
+     int export = 0;
+
+     signal(SIGPIPE, SIG_IGN);
+     log = stdout;
+     display_file = stdout;
+     argc--; argv++;
+     while (argc) {
+         if (strcmp(*argv, "-port") == 0) {
+              argc--; argv++;
+              if (!argc) usage();
+              port = atoi(*argv);
+         } 
+#if defined _WIN32 || 1
+          else if (strcmp(*argv, "-threads") == 0) {
+              argc--; argv++;
+              if (!argc) usage();
+              max_threads = atoi(*argv);
+          } 
+#endif
+          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, "-export") == 0) {
+             export = 1;
+         } else if (strcmp(*argv, "-logfile") == 0) {
+             argc--; argv++;
+             if (!argc) usage();
+             /* Gross hack, but it makes it unnecessary to add an
+                 extra argument to disable logging, and makes the code
+                 more efficient because it doesn't actually write data
+                 to /dev/null. */
+             if (! strcmp(*argv, "/dev/null")) {
+               log = display_file = NULL;
+             }
+             else {
+               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();
+
+#ifdef _WIN32
+    if (max_threads < 1) {
+        fprintf(stderr, "warning: there must be at least one thread\n");
+        max_threads = 1;
+    }
+
+    if (max_threads > 1 && do_inetd)
+        fprintf(stderr, "warning: one thread may be used in conjunction with inetd\n");
+
+    InitHandles();
+#endif
+
+     service_name = *argv;
+
+     if (server_acquire_creds(service_name, &server_creds) < 0)
+        return -1;
+     
+     if (do_inetd) {
+        close(1);
+        close(2);
+
+        sign_server(0, server_creds, export);
+        close(0);
+     } else {
+        int stmp;
+
+        if ((stmp = create_socket(port)) >= 0) {
+             if (listen(stmp, max_threads == 1 ? 0 : max_threads) < 0)
+                 perror("listening on socket");
+
+             do {
+                 struct _work_plan * work = malloc(sizeof(struct _work_plan));
+
+                 if ( work == NULL ) {
+                     fprintf(stderr, "fatal error: out of memory");
+                     break;
+                 }
+
+                 /* Accept a TCP connection */
+                 if ((work->s = accept(stmp, NULL, 0)) < 0) {
+                     perror("accepting connection");
+                     continue;
+                 }
+                  
+                 work->server_creds = server_creds;
+                 work->export = export;
+
+                 if (max_threads == 1) {
+                     worker_bee((void *)work);
+                 } 
+#if defined _WIN32 || 1
+                 else {
+                     if ( WaitAndIncrementThreadCounter() ) {
+#ifdef _WIN32
+                         uintptr_t handle = _beginthread(worker_bee, 0, (void *)work);
+                         if (handle == (uintptr_t)-1) {
+                             closesocket(work->s);
+                             free(work);
+                         }
+#else
+                        int err;
+                        pthread_t thr;
+                        err = pthread_create(&thr, 0, (void *(*)(void *))worker_bee,
+                                             (void *) work);
+                        if (err) {
+                            perror("pthread_create");
+                            closesocket(work->s);
+                            free(work);
+                        }
+                        (void) pthread_detach(thr);
+#endif
+                     } else {
+                         fprintf(stderr, "fatal error incrementing thread counter");
+                         closesocket(work->s);
+                         free(work);
+                         break;
+                     }
+                 }
+#endif
+             } while (!once);
+
+            closesocket(stmp);
+        }
+     }
+
+     (void) gss_release_cred(&min_stat, &server_creds);
+
+#ifdef _WIN32
+    CleanupHandles();
+#else
+    if (max_threads > 1)
+       while (1)
+           sleep (999999);
+#endif
+
+     return 0;
+}