From 275c0f894eef509f3587a928e9957e89a1c1bc72 Mon Sep 17 00:00:00 2001 From: Ken Raeburn Date: Wed, 9 Feb 2005 22:37:38 +0000 Subject: [PATCH] multithreaded gssapi test prog based on gss-sample w/jaltman+raeburn changes git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@17096 dc483132-0cff-0310-8789-dd5450dbe970 --- src/tests/gss-threads/ChangeLog | 6 + src/tests/gss-threads/Makefile.in | 50 ++ src/tests/gss-threads/README | 165 ++++++ src/tests/gss-threads/gss-client.c | 883 +++++++++++++++++++++++++++++ src/tests/gss-threads/gss-misc.c | 423 ++++++++++++++ src/tests/gss-threads/gss-misc.h | 60 ++ src/tests/gss-threads/gss-server.c | 850 +++++++++++++++++++++++++++ 7 files changed, 2437 insertions(+) create mode 100644 src/tests/gss-threads/ChangeLog create mode 100644 src/tests/gss-threads/Makefile.in create mode 100644 src/tests/gss-threads/README create mode 100644 src/tests/gss-threads/gss-client.c create mode 100644 src/tests/gss-threads/gss-misc.c create mode 100644 src/tests/gss-threads/gss-misc.h create mode 100644 src/tests/gss-threads/gss-server.c diff --git a/src/tests/gss-threads/ChangeLog b/src/tests/gss-threads/ChangeLog new file mode 100644 index 000000000..2b4c17558 --- /dev/null +++ b/src/tests/gss-threads/ChangeLog @@ -0,0 +1,6 @@ +2005-02-09 Ken Raeburn + + * 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 index 000000000..1229081a3 --- /dev/null +++ b/src/tests/gss-threads/Makefile.in @@ -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 index 000000000..f555b3e3a --- /dev/null +++ b/src/tests/gss-threads/README @@ -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 index 000000000..f199dc252 --- /dev/null +++ b/src/tests/gss-threads/gss-client.c @@ -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 +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#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 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; icount; 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 index 000000000..c912792e0 --- /dev/null +++ b/src/tests/gss-threads/gss-misc.c @@ -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 +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +/* need struct timeval */ +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif + +#include +#include "gss-misc.h" + +#ifdef HAVE_STDLIB_H +#include +#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 : " 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 +#include + +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 index 000000000..35b3b7390 --- /dev/null +++ b/src/tests/gss-threads/gss-misc.h @@ -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 +#include + +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 index 000000000..6cbac84af --- /dev/null +++ b/src/tests/gss-threads/gss-server.c @@ -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 +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +#include +#include "gss-misc.h" +#include "port-sockets.h" + +#ifdef HAVE_STRING_H +#include +#else +#include +#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; +} -- 2.26.2