diff options
Diffstat (limited to 'src/tests/gss-threads/gss-server.c')
| -rw-r--r-- | src/tests/gss-threads/gss-server.c | 853 |
1 files changed, 853 insertions, 0 deletions
diff --git a/src/tests/gss-threads/gss-server.c b/src/tests/gss-threads/gss-server.c new file mode 100644 index 000000000000..9bae19643844 --- /dev/null +++ b/src/tests/gss-threads/gss-server.c @@ -0,0 +1,853 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * 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,2008 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 "autoconf.h" +#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 *logfile; + +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(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(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, oid_name; + gss_name_t client; + gss_OID doid; + OM_uint32 maj_stat, min_stat, acc_sec_min_stat; + 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 (logfile) { + fprintf(logfile, "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 && logfile) { + fprintf(logfile, "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, NULL); + + if (recv_tok.value) { + free(recv_tok.value); + recv_tok.value = NULL; + } + + if (send_tok.length != 0) { + if (verbose && logfile) { + fprintf(logfile, + "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 (logfile) + fprintf(logfile, "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 && logfile) { + if (maj_stat == GSS_S_CONTINUE_NEEDED) + fprintf(logfile, "continue needed...\n"); + else + fprintf(logfile, "\n"); + fflush(logfile); + } + } while (maj_stat == GSS_S_CONTINUE_NEEDED); + + /* display the flags */ + display_ctx_flags(*ret_flags); + + if (verbose && logfile) { + 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(logfile, "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 (logfile) + fprintf(logfile, "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(u_short port) +{ + struct sockaddr_in saddr; + int s, on = 1; + + saddr.sin_family = AF_INET; + saddr.sin_port = htons(port); + saddr.sin_addr.s_addr = INADDR_ANY; + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 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(struct timeval *tv1, struct timeval *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(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, NULL); + if (verbose && logfile) { + fprintf(logfile, "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 (logfile) { + fprintf(logfile, "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, NULL); + if (verbose && logfile) { + fprintf(logfile, "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(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, ret_flags; + int i, conf_state, token_flags; + char *cp; + + /* 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 (logfile) + fprintf(logfile, "NOOP token\n"); + if (xmit_buf.value) { + free(xmit_buf.value); + xmit_buf.value = 0; + } + break; + } + + if (verbose && logfile) { + fprintf(logfile, "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 (logfile) { + fprintf(logfile, "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, 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 (logfile) { + fprintf(logfile, "Received message: "); + cp = msg_buf.value; + if (isprint((unsigned char)cp[0]) && + isprint((unsigned char)cp[1])) { + fprintf(logfile, "\"%.*s\"\n", (int)msg_buf.length, + (char *)msg_buf.value); + } else { + fprintf(logfile, "\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 (logfile) + fflush(logfile); + + return 0; +} + +static int max_threads = 1; + +#ifdef _WIN32 +static thread_count = 0; +static HANDLE hMutex = NULL; +static HANDLE hEvent = NULL; + +void +init_handles(void) +{ + hMutex = CreateMutex(NULL, FALSE, NULL); + hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); +} + +void +cleanup_handles(void) +{ + CloseHandle(hMutex); + CloseHandle(hEvent); +} + +BOOL +wait_and_increment_thread_counter(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 +decrement_and_signal_thread_counter(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 +wait_and_increment_thread_counter(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) { + pthread_mutex_unlock(&counter_mutex); + perror("pthread_cond_wait"); + return 0; + } + } + counter++; + pthread_mutex_unlock(&counter_mutex); + return 1; +} + +static void +decrement_and_signal_thread_counter(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 = 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) + decrement_and_signal_thread_counter(); +#endif + return NULL; +} + +int +main(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); + logfile = stdout; + display_file = stdout; + argc--; + argv++; + while (argc) { + if (strcmp(*argv, "-port") == 0) { + argc--; + argv++; + if (!argc) + usage(); + port = atoi(*argv); + } else if (strcmp(*argv, "-threads") == 0) { + argc--; + argv++; + if (!argc) + usage(); + max_threads = atoi(*argv); + } else if (strcmp(*argv, "-verbose") == 0) { + verbose = 1; + } else if (strcmp(*argv, "-once") == 0) { + once = 1; + } else if (strcmp(*argv, "-inetd") == 0) { + do_inetd = 1; + } else if (strcmp(*argv, "-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")) { + logfile = display_file = NULL; + } else { + logfile = fopen(*argv, "a"); + display_file = logfile; + if (!logfile) { + 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"); + } + + init_handles(); +#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; + + stmp = create_socket(port); + if (stmp >= 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 */ + work->s = accept(stmp, NULL, 0); + if (work->s < 0) { + perror("accepting connection"); + continue; + } + + work->server_creds = server_creds; + work->export = export; + + if (max_threads == 1) { + worker_bee(work); + } else { + if (wait_and_increment_thread_counter()) { +#ifdef _WIN32 + uintptr_t handle = _beginthread(worker_bee, 0, work); + if (handle == (uintptr_t)-1) { + closesocket(work->s); + free(work); + } +#else + int err; + pthread_t thr; + err = pthread_create(&thr, 0, worker_bee, 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; + } + } + } while (!once); + + closesocket(stmp); + } + } + + (void)gss_release_cred(&min_stat, &server_creds); + +#ifdef _WIN32 + cleanup_handles(); +#else + if (max_threads > 1) { + while (1) + sleep(999999); + } +#endif + + return 0; +} |
