Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -14,7 +14,9 @@ tls.o tlsBIO.o tlsIO.o tlsX509.o tls.tcl.h +tls.tcl.h.new.1 +tls.tcl.h.new.2 build/work dh_params.h Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -8,10 +8,11 @@ INSTALL = @INSTALL@ PACKAGE_VERSION = @PACKAGE_VERSION@ TCL_PACKAGE_PATH = @TCL_PACKAGE_PATH@ PACKAGE_INSTALL_DIR = $(TCL_PACKAGE_PATH)/tcltls$(PACKAGE_VERSION) VPATH = @srcdir@ +srcdir = @srcdir@ all: @EXTENSION_TARGET@ # The shared object target tcltls.@SHOBJEXT@: tls.o tlsBIO.o tlsIO.o tlsX509.o Makefile @@ -22,21 +23,23 @@ $(AR) rcu tcltls.a.new tls.o tlsBIO.o tlsIO.o tlsX509.o $(RANLIB) tcltls.a.new mv tcltls.a.new tcltls.a # Dependencies for all our targets -tls.o: @srcdir@/tls.c @srcdir@/tlsInt.h @srcdir@/tclOpts.h @srcdir@/tls.tcl.h dh_params.h Makefile +tls.o: @srcdir@/tls.c @srcdir@/tlsInt.h @srcdir@/tclOpts.h tls.tcl.h dh_params.h Makefile tlsBIO.o: @srcdir@/tlsBIO.c @srcdir@/tlsInt.h Makefile tlsIO.o: @srcdir@/tlsIO.c @srcdir@/tlsInt.h Makefile tlsX509.o: @srcdir@/tlsX509.c @srcdir@/tlsInt.h Makefile # Create a C-source-ified version of the script resources # for TclTLS so that we only need a single file to enable # this extension -@srcdir@/tls.tcl.h: @srcdir@/tls.tcl - @XXD@ -i < '@srcdir@/tls.tcl' > '@srcdir@/tls.tcl.h.new' - mv '@srcdir@/tls.tcl.h.new' '@srcdir@/tls.tcl.h' +tls.tcl.h: $(srcdir)/tls.tcl + od -A n -v -t xC < '$(srcdir)/tls.tcl' > tls.tcl.h.new.1 + sed 's@ *@@g;s@..@0x&, @g' < tls.tcl.h.new.1 > tls.tcl.h.new.2 + rm -f tls.tcl.h.new.1 + mv tls.tcl.h.new.2 tls.tcl.h # Create default DH parameters dh_params.h: @srcdir@/gen_dh_params Makefile @srcdir@/gen_dh_params @GEN_DH_PARAMS_ARGS@ > dh_params.h.new mv dh_params.h.new dh_params.h @@ -48,16 +51,21 @@ # Install the extension install: @EXTENSION_TARGET@ pkgIndex.tcl $(INSTALL) -d '$(DESTDIR)$(PACKAGE_INSTALL_DIR)' $(INSTALL) -t '$(DESTDIR)$(PACKAGE_INSTALL_DIR)' @EXTENSION_TARGET@ pkgIndex.tcl + +# Test target, run the automated test suite +test: @EXTENSION_TARGET@ + cd tests && @TCLSH_PROG@ all.tcl # Clean the local build directory for rebuild against the same configuration clean: rm -f tls.o tlsBIO.o tlsIO.o tlsX509.o rm -f tcltls.@SHOBJEXT@ rm -f tcltls.a.new tcltls.a + rm -f tls.tcl.h tls.tcl.h.new.1 tls.tcl.h.new.2 # Clean the local build directory back to what it was after unpacking the # distribution tarball distclean: clean rm -f config.log config.status @@ -66,11 +74,10 @@ rm -f tcltls.a.linkadd # Clean the local build directory back to only thing things that exist in # version control system mrproper: distclean - rm -f @srcdir@/tls.tcl.h rm -f @srcdir@/configure @srcdir@/config.sub @srcdir@/config.guess @srcdir@/install-sh rm -f @srcdir@/aclocal.m4 rm -rf @srcdir@/autom4te.cache -.PHONY: all install clean distclean mrproper +.PHONY: all install clean distclean mrproper test ADDED aclocal/ax_check_compile_flag.m4 Index: aclocal/ax_check_compile_flag.m4 ================================================================== --- /dev/null +++ aclocal/ax_check_compile_flag.m4 @@ -0,0 +1,74 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 4 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS Index: build/post.sh ================================================================== --- build/post.sh +++ build/post.sh @@ -1,6 +1,9 @@ #! /usr/bin/env bash + +set -e rm -rf build rm -f autogen.sh +make -f Makefile.in srcdir=. tls.tcl.h exit 0 Index: configure.in ================================================================== --- configure.in +++ configure.in @@ -57,46 +57,56 @@ GEN_DH_PARAMS_ARGS='' fi AC_SUBST(GEN_DH_PARAMS_ARGS) dnl Allow the user to manually disable protocols -dnl ## SSLv2: Disabled by default -tcltls_ssl_ssl2='false' -AC_ARG_ENABLE([sslv2], AS_HELP_STRING([--enable-sslv2], [enable SSLv2 protocol]), [ +dnl ## SSLv2: Enabled by default +tcltls_ssl_ssl2='true' +AC_ARG_ENABLE([sslv2], AS_HELP_STRING([--disable-sslv2], [disable SSLv2 protocol]), [ if test "$enableval" = "yes"; then tcltls_ssl_ssl2='force' + else + tcltls_ssl_ssl2='false' fi ]) -dnl ## SSLv3: Disabled by default -tcltls_ssl_ssl3='false' -AC_ARG_ENABLE([sslv3], AS_HELP_STRING([--enable-sslv3], [enable SSLv3 protocol]), [ +dnl ## SSLv3: Enabled by default +tcltls_ssl_ssl3='true' +AC_ARG_ENABLE([sslv3], AS_HELP_STRING([--disable-sslv3], [disable SSLv3 protocol]), [ if test "$enableval" = "yes"; then tcltls_ssl_ssl3='force' + else + tcltls_ssl_ssl3='false' fi ]) dnl ## TLSv1.0: Enabled by default tcltls_ssl_tls1_0='true' AC_ARG_ENABLE([tlsv1.0], AS_HELP_STRING([--disable-tlsv1.0], [disable TLSv1.0 protocol]), [ - if test "$enableval" = "no"; then + if test "$enableval" = "yes"; then + tcltls_ssl_tls1_0='force' + else tcltls_ssl_tls1_0='false' fi ]) dnl ## TLSv1.1: Enabled by default tcltls_ssl_tls1_1='true' AC_ARG_ENABLE([tlsv1.1], AS_HELP_STRING([--disable-tlsv1.1], [disable TLSv1.1 protocol]), [ - if test "$enableval" = "no"; then + if test "$enableval" = "yes"; then + tcltls_ssl_tls1_1='force' + else tcltls_ssl_tls1_1='false' fi ]) dnl ## TLSv1.1: Enabled by default tcltls_ssl_tls1_2='true' AC_ARG_ENABLE([tlsv1.2], AS_HELP_STRING([--disable-tlsv1.2], [disable TLSv1.2 protocol]), [ - if test "$enableval" = "no"; then + if test "$enableval" = "yes"; then + tcltls_ssl_tls1_2='force' + else tcltls_ssl_tls1_2='false' fi ]) dnl Enable support for a debugging build @@ -107,28 +117,23 @@ fi ]) if test "$tcltls_debug" = 'true'; then AC_DEFINE(TCLEXT_TCLTLS_DEBUG, [1], [Enable debugging build]) AX_CHECK_COMPILE_FLAG([-fcheck-pointer-bounds], [CFLAGS="$CFLAGS -fcheck-pointer-bounds"]) - AX_CHECK_COMPILE_FLAG([-fsanitize=address], [CFLAGS="$CFLAGS -fsanitize=address"]) - AX_CHECK_COMPILE_FLAG([-fsanitize=undefined], [CFLAGS="$CFLAGS -fsanitize=undefined"]) else dnl If we are not doing debugging disable some of the more annoying warnings AX_CHECK_COMPILE_FLAG([-Wno-unused-value], [CFLAGS="$CFLAGS -Wno-unused-value"]) AX_CHECK_COMPILE_FLAG([-Wno-unused-parameter], [CFLAGS="$CFLAGS -Wno-unused-parameter"]) AX_CHECK_COMPILE_FLAG([-Wno-deprecated-declarations], [CFLAGS="$CFLAGS -Wno-deprecated-declarations"]) fi -dnl Find "xxd" so we can build the tls.tcl.h file -AC_CHECK_PROG([XXD], [xxd], [xxd], [__xxd__not__found]) - dnl Find "pkg-config" since we need to use it AC_CHECK_TOOL([PKGCONFIG], [pkg-config], [false]) dnl Determine if we have been asked to use a fast path if possible tcltls_ssl_fastpath='yes' -AC_ARG_ENABLE([ssl-fastpath], AS_HELP_STRING([--disable-ssl-fast-path], [disable using the underlying file descriptor for talking directly to the SSL library]), [ +AC_ARG_ENABLE([ssl-fastpath], AS_HELP_STRING([--disable-ssl-fastpath], [disable using the underlying file descriptor for talking directly to the SSL library]), [ if test "$enableval" = 'no'; then tcltls_ssl_fastpath='no' fi ]) Index: tls.c ================================================================== --- tls.c +++ tls.c @@ -130,11 +130,11 @@ } else { /* dprintf("Called to unlock (n=%i of %i)", n, locksCount); */ Tcl_MutexUnlock(&locks[n]); } - dprintf("Returning"); + /* dprintf("Returning"); */ return; file = file; line = line; } @@ -634,10 +634,11 @@ Tcl_Obj *CONST objv[]; { Tcl_Channel chan; /* The channel to set a mode on. */ State *statePtr; /* client state for ssl socket */ int ret = 1; + int err = 0; dprintf("Called"); if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel"); @@ -658,20 +659,20 @@ "\": not a TLS channel", NULL); return TCL_ERROR; } statePtr = (State *)Tcl_GetChannelInstanceData(chan); - if (!SSL_is_init_finished(statePtr->ssl)) { - int err = 0; dprintf("Calling Tls_WaitForConnect"); - ret = Tls_WaitForConnect(statePtr, &err); + ret = Tls_WaitForConnect(statePtr, &err, 1); dprintf("Tls_WaitForConnect returned: %i", ret); + if (ret < 0) { if ((statePtr->flags & TLS_TCL_ASYNC) && err == EAGAIN) { dprintf("Async set and err = EAGAIN"); ret = 0; } + } if (ret < 0) { CONST char *errStr = statePtr->err; Tcl_ResetResult(interp); Tcl_SetErrno(err); @@ -681,13 +682,15 @@ } Tcl_AppendResult(interp, "handshake failed: ", errStr, (char *) NULL); dprintf("Returning TCL_ERROR with handshake failed: %s", errStr); return TCL_ERROR; - } - } + } else { + ret = 1; + } + dprintf("Returning TCL_OK with data \"%i\"", ret); Tcl_SetObjResult(interp, Tcl_NewIntObj(ret)); return TCL_OK; clientData = clientData; } @@ -732,39 +735,32 @@ char *DHparams = NULL; char *model = NULL; #ifndef OPENSSL_NO_TLSEXT char *servername = NULL; /* hostname for Server Name Indication */ #endif -#if defined(NO_SSL2) - int ssl2 = 0; -#else - int ssl2 = 1; -#endif -#if defined(NO_SSL3) - int ssl3 = 0; -#else - int ssl3 = 1; -#endif -#if defined(NO_TLS1) - int tls1 = 0; -#else - int tls1 = 1; -#endif -#if defined(NO_TLS1_1) - int tls1_1 = 0; -#else - int tls1_1 = 1; -#endif -#if defined(NO_TLS1_2) - int tls1_2 = 0; -#else - int tls1_2 = 1; -#endif + int ssl2 = 0, ssl3 = 0; + int tls1 = 1, tls1_1 = 1, tls1_2 = 1; int proto = 0; int verify = 0, require = 0, request = 1; dprintf("Called"); + +#if defined(NO_TLS1) && defined(NO_TLS1_1) && defined(NO_TLS1_2) && defined(NO_SSL3) && !defined(NO_SSL2) + ssl2 = 1; +#endif +#if defined(NO_TLS1) && defined(NO_TLS1_1) && defined(NO_TLS1_2) && defined(NO_SSL2) && !defined(NO_SSL3) + ssl3 = 1; +#endif +#if defined(NO_TLS1) + tls1 = 0; +#endif +#if defined(NO_TLS1_1) + tls1_1 = 0; +#endif +#if defined(NO_TLS1_2) + tls1_2 = 0; +#endif if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel ?options?"); return TCL_ERROR; } @@ -1660,11 +1656,11 @@ */ int Tls_Init(Tcl_Interp *interp) { const char tlsTclInitScript[] = { #include "tls.tcl.h" - , 0x00 + 0x00 }; dprintf("Called"); /* Index: tlsIO.c ================================================================== --- tlsIO.c +++ tlsIO.c @@ -173,10 +173,188 @@ return(TCL_OK); /* Interp is unused. */ interp = interp; } + +/* + *------------------------------------------------------* + * + * Tls_WaitForConnect -- + * + * Sideeffects: + * Issues SSL_accept or SSL_connect + * + * Result: + * None. + * + *------------------------------------------------------* + */ +int Tls_WaitForConnect(State *statePtr, int *errorCodePtr, int handshakeFailureIsPermanent) { + unsigned long backingError; + int err, rc; + int bioShouldRetry; + + dprintf("WaitForConnect(%p)", (void *) statePtr); + dprintFlags(statePtr); + + if (!(statePtr->flags & TLS_TCL_INIT)) { + dprintf("Tls_WaitForConnect called on already initialized channel -- returning with immediate success"); + *errorCodePtr = 0; + return(0); + } + + if (statePtr->flags & TLS_TCL_HANDSHAKE_FAILED) { + /* + * Different types of operations have different requirements + * SSL being established + */ + if (handshakeFailureIsPermanent) { + dprintf("Asked to wait for a TLS handshake that has already failed. Returning fatal error"); + *errorCodePtr = ECONNABORTED; + } else { + dprintf("Asked to wait for a TLS handshake that has already failed. Returning soft error"); + *errorCodePtr = ECONNRESET; + } + return(-1); + } + + for (;;) { + /* Not initialized yet! */ + if (statePtr->flags & TLS_TCL_SERVER) { + dprintf("Calling SSL_accept()"); + + err = SSL_accept(statePtr->ssl); + } else { + dprintf("Calling SSL_connect()"); + + err = SSL_connect(statePtr->ssl); + } + + if (err > 0) { + dprintf("That seems to have gone okay"); + + err = BIO_flush(statePtr->bio); + + if (err <= 0) { + dprintf("Flushing the lower layers failed, this will probably terminate this session"); + } + } + + rc = SSL_get_error(statePtr->ssl, err); + + dprintf("Got error: %i (rc = %i)", err, rc); + + bioShouldRetry = 0; + if (err <= 0) { + if (rc == SSL_ERROR_WANT_CONNECT || rc == SSL_ERROR_WANT_ACCEPT || rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) { + bioShouldRetry = 1; + } else if (BIO_should_retry(statePtr->bio)) { + bioShouldRetry = 1; + } else if (rc == SSL_ERROR_SYSCALL && Tcl_GetErrno() == EAGAIN) { + bioShouldRetry = 1; + } + } else { + if (!SSL_is_init_finished(statePtr->ssl)) { + bioShouldRetry = 1; + } + } + + if (bioShouldRetry) { + dprintf("The I/O did not complete -- but we should try it again"); + + if (statePtr->flags & TLS_TCL_ASYNC) { + dprintf("Returning EAGAIN so that it can be retried later"); + + *errorCodePtr = EAGAIN; + + return(-1); + } else { + dprintf("Doing so now"); + + continue; + } + } + + dprintf("We have either completely established the session or completely failed it -- there is no more need to ever retry it though"); + break; + } + + + *errorCodePtr = EINVAL; + + switch (rc) { + case SSL_ERROR_NONE: + /* The connection is up, we are done here */ + dprintf("The connection is up"); + break; + case SSL_ERROR_ZERO_RETURN: + dprintf("SSL_ERROR_ZERO_RETURN: Connect returned an invalid value...") + return(-1); + case SSL_ERROR_SYSCALL: + backingError = ERR_get_error(); + + if (backingError == 0 && err == 0) { + dprintf("EOF reached") + *errorCodePtr = ECONNRESET; + } else if (backingError == 0 && err == -1) { + dprintf("I/O error occured (errno = %lu)", (unsigned long) Tcl_GetErrno()); + *errorCodePtr = Tcl_GetErrno(); + if (*errorCodePtr == ECONNRESET) { + *errorCodePtr = ECONNABORTED; + } + } else { + dprintf("I/O error occured (backingError = %lu)", backingError); + *errorCodePtr = backingError; + if (*errorCodePtr == ECONNRESET) { + *errorCodePtr = ECONNABORTED; + } + } + + statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; + + return(-1); + case SSL_ERROR_SSL: + dprintf("Got permanent fatal SSL error, aborting immediately"); + Tls_Error(statePtr, (char *)ERR_reason_error_string(ERR_get_error())); + statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; + *errorCodePtr = ECONNABORTED; + return(-1); + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + case SSL_ERROR_WANT_X509_LOOKUP: + default: + dprintf("We got a confusing reply: %i", rc); + *errorCodePtr = Tcl_GetErrno(); + dprintf("ERR(%d, %d) ", rc, *errorCodePtr); + return(-1); + } + +#if 0 + if (statePtr->flags & TLS_TCL_SERVER) { + dprintf("This is an TLS server, checking the certificate for the peer"); + + err = SSL_get_verify_result(statePtr->ssl); + if (err != X509_V_OK) { + dprintf("Invalid certificate, returning in failure"); + + Tls_Error(statePtr, (char *)X509_verify_cert_error_string(err)); + statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; + *errorCodePtr = ECONNABORTED; + return(-1); + } + } +#endif + + dprintf("Removing the \"TLS_TCL_INIT\" flag since we have completed the handshake"); + statePtr->flags &= ~TLS_TCL_INIT; + + dprintf("Returning in success"); + *errorCodePtr = 0; + + return(0); +} /* *------------------------------------------------------------------- * * TlsInputProc -- @@ -194,10 +372,11 @@ * *------------------------------------------------------------------- */ static int TlsInputProc(ClientData instanceData, char *buf, int bufSize, int *errorCodePtr) { + unsigned long backingError; State *statePtr = (State *) instanceData; int bytesRead; int tlsConnect; int err; @@ -210,26 +389,23 @@ dprintf("Callback is running, reading 0 bytes"); return(0); } dprintf("Calling Tls_WaitForConnect"); - tlsConnect = Tls_WaitForConnect(statePtr, errorCodePtr); + tlsConnect = Tls_WaitForConnect(statePtr, errorCodePtr, 0); if (tlsConnect < 0) { - dprintf("Got an error (bytesRead = %i)", bytesRead); + dprintf("Got an error waiting to connect (tlsConnect = %i, *errorCodePtr = %i)", tlsConnect, *errorCodePtr); + bytesRead = -1; if (*errorCodePtr == ECONNRESET) { dprintf("Got connection reset"); /* Soft EOF */ *errorCodePtr = 0; bytesRead = 0; } - return(0); - } - - if (statePtr->flags & TLS_TCL_INIT) { - statePtr->flags &= ~(TLS_TCL_INIT); + return(bytesRead); } /* * We need to clear the SSL error stack now because we sometimes reach * this function with leftover errors in the stack. If BIO_read @@ -245,25 +421,56 @@ bytesRead = BIO_read(statePtr->bio, buf, bufSize); dprintf("BIO_read -> %d", bytesRead); err = SSL_get_error(statePtr->ssl, bytesRead); - if (BIO_should_retry(statePtr->bio)) { - dprintf("I/O failed, will retry based on EAGAIN"); - *errorCodePtr = EAGAIN; +#if 0 + if (bytesRead <= 0) { + if (BIO_should_retry(statePtr->bio)) { + dprintf("I/O failed, will retry based on EAGAIN"); + *errorCodePtr = EAGAIN; + } } +#endif switch (err) { case SSL_ERROR_NONE: dprintBuffer(buf, bytesRead); break; case SSL_ERROR_SSL: + dprintf("SSL negotiation error, indicating that the connection has been aborted"); + Tls_Error(statePtr, TCLTLS_SSL_ERROR(statePtr->ssl, bytesRead)); *errorCodePtr = ECONNABORTED; + bytesRead = -1; + break; case SSL_ERROR_SYSCALL: - dprintf("I/O error reading, treating it as EOF"); + backingError = ERR_get_error(); + + if (backingError == 0 && bytesRead == 0) { + dprintf("EOF reached") + *errorCodePtr = 0; + bytesRead = 0; + } else if (backingError == 0 && bytesRead == -1) { + dprintf("I/O error occured (errno = %lu)", (unsigned long) Tcl_GetErrno()); + *errorCodePtr = Tcl_GetErrno(); + bytesRead = -1; + } else { + dprintf("I/O error occured (backingError = %lu)", backingError); + *errorCodePtr = backingError; + bytesRead = -1; + } + + break; + case SSL_ERROR_ZERO_RETURN: + dprintf("Got SSL_ERROR_ZERO_RETURN, this means an EOF has been reached"); + bytesRead = 0; + *errorCodePtr = 0; + break; + default: + dprintf("Unknown error (err = %i), mapping to EOF", err); *errorCodePtr = 0; bytesRead = 0; break; } @@ -288,12 +495,14 @@ * *------------------------------------------------------------------- */ static int TlsOutputProc(ClientData instanceData, CONST char *buf, int toWrite, int *errorCodePtr) { + unsigned long backingError; State *statePtr = (State *) instanceData; int written, err; + int tlsConnect; *errorCodePtr = 0; dprintf("BIO_write(%p, %d)", (void *) statePtr, toWrite); dprintBuffer(buf, toWrite); @@ -304,15 +513,23 @@ *errorCodePtr = EAGAIN; return(-1); } dprintf("Calling Tls_WaitForConnect"); - written = Tls_WaitForConnect(statePtr, errorCodePtr); - if (written < 0) { - dprintf("Tls_WaitForConnect returned %i (err = %i)", written, *errorCodePtr); + tlsConnect = Tls_WaitForConnect(statePtr, errorCodePtr, 1); + if (tlsConnect < 0) { + dprintf("Got an error waiting to connect (tlsConnect = %i, *errorCodePtr = %i)", tlsConnect, *errorCodePtr); + + written = -1; + if (*errorCodePtr == ECONNRESET) { + dprintf("Got connection reset"); + /* Soft EOF */ + *errorCodePtr = 0; + written = 0; + } - return(-1); + return(written); } if (toWrite == 0) { dprintf("zero-write"); err = BIO_flush(statePtr->bio); @@ -362,15 +579,29 @@ dprintf(" write X BLOCK"); break; case SSL_ERROR_ZERO_RETURN: dprintf(" closed"); written = 0; + *errorCodePtr = 0; break; case SSL_ERROR_SYSCALL: - *errorCodePtr = Tcl_GetErrno(); - dprintf(" [%d] syscall errr: %d", written, *errorCodePtr); - written = -1; + backingError = ERR_get_error(); + + if (backingError == 0 && written == 0) { + dprintf("EOF reached") + *errorCodePtr = 0; + written = 0; + } else if (backingError == 0 && written == -1) { + dprintf("I/O error occured (errno = %lu)", (unsigned long) Tcl_GetErrno()); + *errorCodePtr = Tcl_GetErrno(); + written = -1; + } else { + dprintf("I/O error occured (backingError = %lu)", backingError); + *errorCodePtr = backingError; + written = -1; + } + break; case SSL_ERROR_SSL: Tls_Error(statePtr, TCLTLS_SSL_ERROR(statePtr->ssl, written)); *errorCodePtr = ECONNABORTED; written = -1; @@ -583,11 +814,11 @@ return 0; } dprintf("Calling Tls_WaitForConnect"); errorCode = 0; - if (Tls_WaitForConnect(statePtr, &errorCode) < 0) { + if (Tls_WaitForConnect(statePtr, &errorCode, 1) < 0) { if (errorCode == EAGAIN) { dprintf("Async flag could be set (didn't check) and errorCode == EAGAIN: Returning 0"); return 0; } @@ -727,175 +958,10 @@ dprintf("Returning"); return; } -/* - *------------------------------------------------------* - * - * Tls_WaitForConnect -- - * - * Sideeffects: - * Issues SSL_accept or SSL_connect - * - * Result: - * None. - * - *------------------------------------------------------* - */ -int Tls_WaitForConnect(State *statePtr, int *errorCodePtr) { - unsigned long backingError; - int err, rc; - int bioShouldRetry; - - dprintf("WaitForConnect(%p)", (void *) statePtr); - dprintFlags(statePtr); - - if (!(statePtr->flags & TLS_TCL_INIT)) { - dprintf("Tls_WaitForConnect called on already initialized channel -- returning with immediate success"); - *errorCodePtr = 0; - return(0); - } - - if (statePtr->flags & TLS_TCL_HANDSHAKE_FAILED) { - /* - * We choose ECONNRESET over ECONNABORTED here because some server - * side code, on the wiki for example, sets up a read handler that - * does a read and if eof closes the channel. There is no catch/try - * around the reads so exceptions will result in potentially many - * dangling channels hanging around that should have been closed. - * (Backgroun: ECONNABORTED maps to a Tcl exception and - * ECONNRESET maps to graceful EOF). - */ - *errorCodePtr = ECONNRESET; - return(-1); - } - - for (;;) { - /* Not initialized yet! */ - if (statePtr->flags & TLS_TCL_SERVER) { - dprintf("Calling SSL_accept()"); - - err = SSL_accept(statePtr->ssl); - } else { - dprintf("Calling SSL_connect()"); - - err = SSL_connect(statePtr->ssl); - } - - if (err > 0) { - dprintf("That seems to have gone okay"); - - err = BIO_flush(statePtr->bio); - - if (err <= 0) { - dprintf("Flushing the lower layers failed, this will probably terminate this session"); - } - } - - rc = SSL_get_error(statePtr->ssl, err); - - dprintf("Got error: %i (rc = %i)", err, rc); - - bioShouldRetry = 0; - if (err <= 0) { - if (rc == SSL_ERROR_WANT_CONNECT || rc == SSL_ERROR_WANT_ACCEPT || rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) { - bioShouldRetry = 1; - } else if (BIO_should_retry(statePtr->bio)) { - bioShouldRetry = 1; - } else if (rc == SSL_ERROR_SYSCALL && Tcl_GetErrno() == EAGAIN) { - bioShouldRetry = 1; - } - } else { - if (!SSL_is_init_finished(statePtr->ssl)) { - bioShouldRetry = 1; - } - } - - if (bioShouldRetry) { - dprintf("The I/O did not complete -- but we should try it again"); - - if (statePtr->flags & TLS_TCL_ASYNC) { - dprintf("Returning EAGAIN so that it can be retried later"); - - *errorCodePtr = EAGAIN; - - return(-1); - } else { - dprintf("Doing so now"); - - continue; - } - } - - dprintf("We have either completely established the session or completely failed it -- there is no more need to ever retry it though"); - break; - } - - - *errorCodePtr = EINVAL; - - switch (rc) { - case SSL_ERROR_NONE: - /* The connection is up, we are done here */ - dprintf("The connection is up"); - break; - case SSL_ERROR_ZERO_RETURN: - dprintf("SSL_ERROR_ZERO_RETURN: Connect returned an invalid value...") - return(-1); - case SSL_ERROR_SYSCALL: - backingError = ERR_get_error(); - dprintf("I/O error occured"); - - if (backingError == 0 && err == 0) { - dprintf("EOF reached") - } - - statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; - *errorCodePtr = ECONNRESET; - return(-1); - case SSL_ERROR_SSL: - dprintf("Got permanent fatal SSL error, aborting immediately"); - Tls_Error(statePtr, (char *)ERR_reason_error_string(ERR_get_error())); - statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; - *errorCodePtr = ECONNABORTED; - return(-1); - case SSL_ERROR_WANT_CONNECT: - case SSL_ERROR_WANT_ACCEPT: - case SSL_ERROR_WANT_X509_LOOKUP: - default: - dprintf("We got a confusing reply: %i", rc); - *errorCodePtr = Tcl_GetErrno(); - dprintf("ERR(%d, %d) ", rc, *errorCodePtr); - return(-1); - } - -#if 0 - if (statePtr->flags & TLS_TCL_SERVER) { - dprintf("This is an TLS server, checking the certificate for the peer"); - - err = SSL_get_verify_result(statePtr->ssl); - if (err != X509_V_OK) { - dprintf("Invalid certificate, returning in failure"); - - Tls_Error(statePtr, (char *)X509_verify_cert_error_string(err)); - statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; - *errorCodePtr = ECONNABORTED; - return(-1); - } - } -#endif - - dprintf("Removing the \"TLS_TCL_INIT\" flag since we have completed the handshake"); - statePtr->flags &= ~TLS_TCL_INIT; - - dprintf("Returning in success"); - *errorCodePtr = 0; - - return(0); -} - Tcl_Channel Tls_GetParent(State *statePtr, int maskFlags) { dprintf("Requested to get parent of channel %p", statePtr->self); if ((statePtr->flags & ~maskFlags) & TLS_TCL_FASTPATH) { dprintf("Asked to get the parent channel while we are using FastPath -- returning NULL"); Index: tlsInt.h ================================================================== --- tlsInt.h +++ tlsInt.h @@ -169,11 +169,11 @@ Tcl_Obj *Tls_NewX509Obj(Tcl_Interp *interp, X509 *cert); void Tls_Error(State *statePtr, char *msg); void Tls_Free(char *blockPtr); void Tls_Clean(State *statePtr); -int Tls_WaitForConnect(State *statePtr, int *errorCodePtr); +int Tls_WaitForConnect(State *statePtr, int *errorCodePtr, int handshakeFailureIsPermanent); BIO *BIO_new_tcl(State* statePtr, int flags); #define PTR2INT(x) ((int) ((intptr_t) (x)))