Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -14,7 +14,9 @@ tls.o tls_openssl_bio.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 @@ -3,14 +3,19 @@ RANLIB = @RANLIB@ CFLAGS = @CFLAGS@ @SHOBJFLAGS@ CPPFLAGS = @CPPFLAGS@ -I@srcdir@ -I. @DEFS@ @TCL_DEFS@ LDFLAGS = @LDFLAGS@ @SHOBJLDFLAGS@ LIBS = @LIBS@ -INSTALL = @INSTALL@ PACKAGE_VERSION = @PACKAGE_VERSION@ +prefix = @prefix@ +exec_prefix = @exec_prefix@ +libdir = @libdir@ TCL_PACKAGE_PATH = @TCL_PACKAGE_PATH@ PACKAGE_INSTALL_DIR = $(TCL_PACKAGE_PATH)/tcltls$(PACKAGE_VERSION) +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ VPATH = @srcdir@ all: @EXTENSION_TARGET@ # The shared object target @@ -22,21 +27,23 @@ $(AR) rcu tcltls.a.new tls.o tls_@TCLTLS_SSL_LIB@_bio.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 tls_openssl_bio.o: @srcdir@/tls_openssl_bio.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 Makefile + 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 @@ -47,17 +54,30 @@ $(CC) $(CPPFLAGS) $(CFLAGS) -o "$@" -c "$<" # Install the extension install: @EXTENSION_TARGET@ pkgIndex.tcl $(INSTALL) -d '$(DESTDIR)$(PACKAGE_INSTALL_DIR)' - $(INSTALL) -t '$(DESTDIR)$(PACKAGE_INSTALL_DIR)' @EXTENSION_TARGET@ pkgIndex.tcl + $(INSTALL_PROGRAM) @EXTENSION_TARGET@ '$(DESTDIR)$(PACKAGE_INSTALL_DIR)' + $(INSTALL_DATA) pkgIndex.tcl '$(DESTDIR)$(PACKAGE_INSTALL_DIR)' + +# A convienent helper to undo the installation just done +uninstall: + rm -f '$(DESTDIR)$(PACKAGE_INSTALL_DIR)/@EXTENSION_TARGET@' + rm -f '$(DESTDIR)$(PACKAGE_INSTALL_DIR)/pkgIndex.tcl' + -rmdir '$(DESTDIR)$(PACKAGE_INSTALL_DIR)' + +# Test target, run the automated test suite +test: @EXTENSION_TARGET@ + @TCLSH_PROG@ @srcdir@/tests/all.tcl $(TESTFLAGS) -load "lappend auto_path $(shell pwd)" # Clean the local build directory for rebuild against the same configuration clean: rm -f tls.o tls_openssl_bio.o tlsIO.o tlsX509.o rm -f tcltls.@SHOBJEXT@ + rm -f tcltls.@SHOBJEXT@.a tcltls.@SHOBJEXT@.def 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 +86,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 uninstall 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: aclocal/shobj.m4 ================================================================== --- aclocal/shobj.m4 +++ aclocal/shobj.m4 @@ -39,32 +39,28 @@ DC_CHK_OS_INFO AC_MSG_CHECKING(how to create shared objects) if test -z "$SHOBJFLAGS" -a -z "$SHOBJLDFLAGS" -a -z "$SHOBJCPPFLAGS"; then - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -rdynamic], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -rdynamic -mimpure-text], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -mimpure-text], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -rdynamic -Wl,-G,-z,textoff], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -Wl,-G], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -dynamiclib -flat_namespace -undefined suppress -bind_at_load], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-dynamiclib -flat_namespace -undefined suppress -bind_at_load], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-Wl,-dynamiclib -Wl,-flat_namespace -Wl,-undefined,suppress -Wl,-bind_at_load], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-dynamiclib -flat_namespace -undefined suppress], [ - DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-dynamiclib], [ - AC_MSG_RESULT(cant) - AC_MSG_ERROR([We are unable to make shared objects.]) - ]) - ]) - ]) - ]) - ]) - ]) - ]) - ]) - ]) + DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared], [ + DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -mimpure-text], [ + DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -rdynamic -Wl,-G,-z,textoff], [ + DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -Wl,-G], [ + DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-shared -dynamiclib -flat_namespace -undefined suppress -bind_at_load], [ + DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-dynamiclib -flat_namespace -undefined suppress -bind_at_load], [ + DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-Wl,-dynamiclib -Wl,-flat_namespace -Wl,-undefined,suppress -Wl,-bind_at_load], [ + DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-dynamiclib -flat_namespace -undefined suppress], [ + DC_TEST_SHOBJFLAGS([-fPIC], [-DPIC], [-dynamiclib], [ + AC_MSG_RESULT(cant) + AC_MSG_ERROR([We are unable to make shared objects.]) + ]) + ]) + ]) + ]) + ]) + ]) + ]) ]) ]) fi AC_MSG_RESULT($SHOBJCPPFLAGS $SHOBJFLAGS $SHOBJLDFLAGS) Index: aclocal/tcl.m4 ================================================================== --- aclocal/tcl.m4 +++ aclocal/tcl.m4 @@ -111,10 +111,17 @@ TCLEXT_LOAD_TCLCONFIG AC_DEFINE_UNQUOTED([MODULE_SCOPE], [static], [Define how to declare a function should only be visible to the current module]) TCLEXT_BUILD='shared' + AC_ARG_ENABLE([shared], AS_HELP_STRING([--disable-shared], [disable the shared build (same as --enable-static)]), [ + if test "$enableval" = "no"; then + TCLEXT_BUILD='static' + TCL_SUPPORTS_STUBS=0 + fi + ]) + AC_ARG_ENABLE([static], AS_HELP_STRING([--enable-static], [enable a static build]), [ if test "$enableval" = "yes"; then TCLEXT_BUILD='static' TCL_SUPPORTS_STUBS=0 fi @@ -154,10 +161,14 @@ )` TCL_DEFS="${TCL_DEFS_TCL_ONLY}" AC_SUBST(TCL_DEFS) dnl Needed for package installation - TCL_PACKAGE_PATH="`echo "${TCL_PACKAGE_PATH}" | sed 's@ *$''@@' | awk '{ print [$]1 }'`" + if test "$prefix" = 'NONE' -a "$exec_prefix" = 'NONE' -a "${libdir}" = '${exec_prefix}/lib'; then + TCL_PACKAGE_PATH="`echo "${TCL_PACKAGE_PATH}" | sed 's@ *$''@@' | awk '{ print [$]1 }'`" + else + TCL_PACKAGE_PATH='${libdir}' + fi AC_SUBST(TCL_PACKAGE_PATH) AC_SUBST(LIBS) ]) Index: autogen.sh ================================================================== --- autogen.sh +++ autogen.sh @@ -11,10 +11,11 @@ urls=( http://chiselapp.com/user/rkeene/repository/autoconf/doc/trunk/tcl.m4 http://chiselapp.com/user/rkeene/repository/autoconf/doc/trunk/shobj.m4 http://chiselapp.com/user/rkeene/repository/autoconf/doc/trunk/versionscript.m4 + 'http://git.savannah.gnu.org/gitweb/?p=autoconf-archive.git;a=blob_plain;f=m4/ax_check_compile_flag.m4' ) localFiles=( aclocal/tcltls_openssl.m4 ) @@ -66,9 +67,13 @@ else mv aclocal.m4.new aclocal.m4 fi automake --add-missing --copy --force-missing >/dev/null 2>/dev/null +if ! [ -f install-sh -o -f install.sh -o -f shtool ]; then + echo "automake failed" >&2 + exit 1 +fi autoconf rm -rf autom4te.cache Index: build/post.sh ================================================================== --- build/post.sh +++ build/post.sh @@ -1,6 +1,8 @@ #! /usr/bin/env bash + +set -e rm -rf build rm -f autogen.sh exit 0 ADDED configure.ac Index: configure.ac ================================================================== --- /dev/null +++ configure.ac @@ -0,0 +1,214 @@ +dnl Define ourselves +AC_INIT(tcltls, 1.7.3) + +dnl Checks for programs. +AC_PROG_CC +AC_PROG_MAKE_SET +AC_PROG_INSTALL +AC_GNU_SOURCE + +dnl Determine system information +DC_CHK_OS_INFO + +dnl Look for appropriate headers +AC_CHECK_HEADERS(unistd.h stdlib.h string.h strings.h) + +dnl Perform Tcl Extension required stuff +TCLEXT_INIT + +if test "$TCLEXT_BUILD" != 'static'; then + dnl Determine how to make shared objects + DC_GET_SHOBJFLAGS + + EXTENSION_TARGET="tcltls.${SHOBJEXT}" +else + AC_CHECK_TOOL([AR], [ar], [false]) + AC_CHECK_TOOL([RANLIB], [ranlib], [:]) + EXTENSION_TARGET="tcltls.${AREXT}" +fi +AC_SUBST(EXTENSION_TARGET) +AC_SUBST(TCLEXT_BUILD) + +dnl Determine what SSL library to link with +AC_ARG_WITH([ssl], AS_HELP_STRING([--with-ssl=], [name of ssl library to build against (openssl, libressl, nss, auto)]), [ + if test "$withval" = "no"; then + AC_MSG_ERROR([You may not specify --without-ssl]) + fi + + if test "$withval" = "yes"; then + AC_MSG_ERROR([If you specify --with-ssl then you must provide a value]) + fi + + tcltls_ssl_lib="$withval" +], [ + tcltls_ssl_lib='auto' +]) + +dnl Enable support for building the same library every time +tcltls_deterministic='false' +AC_ARG_ENABLE([deterministic], AS_HELP_STRING([--enable-deterministic], [enable deterministic parameters]), [ + if test "$enableval" = "yes"; then + tcltls_deterministic='true' + fi +]) +if test "$tcltls_deterministic" = 'true'; then + GEN_DH_PARAMS_ARGS='fallback' +else + GEN_DH_PARAMS_ARGS='' +fi +AC_SUBST(GEN_DH_PARAMS_ARGS) + +dnl Allow the user to manually disable protocols +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: 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" = "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" = "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" = "yes"; then + tcltls_ssl_tls1_2='force' + else + tcltls_ssl_tls1_2='false' + fi +]) + +dnl Enable support for a debugging build +tcltls_debug='false' +AC_ARG_ENABLE([debug], AS_HELP_STRING([--enable-debug], [enable debugging parameters]), [ + if test "$enableval" = "yes"; then + tcltls_debug='true' + 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"]) +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 "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='no' +AC_ARG_ENABLE([ssl-fastpath], AS_HELP_STRING([--enable-ssl-fastpath], [enable using the underlying file descriptor for talking directly to the SSL library]), [ + if test "$enableval" = 'yes'; then + tcltls_ssl_fastpath='yes' + else + tcltls_ssl_fastpath='no' + fi +]) + +if test "$tcltls_ssl_fastpath" = 'yes'; then + AC_DEFINE(TCLTLS_SSL_USE_FASTPATH, [1], [Define this to enable using the underlying file descriptor for talking directly to the SSL library]) +fi + +dnl Determine if we have been asked to statically link to the SSL library +TCLEXT_TLS_STATIC_SSL='no' +AC_ARG_ENABLE([static-ssl], AS_HELP_STRING([--enable-static-ssl], [enable statically linking to the specified SSL library]), [ + if test "$enableval" = 'yes'; then + TCLEXT_TLS_STATIC_SSL='yes' + fi +]) + +dnl Enable compiler warnings +AX_CHECK_COMPILE_FLAG([-Wall], [CFLAGS="$CFLAGS -Wall"]) +AX_CHECK_COMPILE_FLAG([-W], [ + CFLAGS="$CFLAGS -W" + AX_CHECK_COMPILE_FLAG([-Wno-self-assign], [CFLAGS="$CFLAGS -Wno-self-assign"]) +]) + +dnl Enable hardening +AX_CHECK_COMPILE_FLAG([-fstack-protector-all], [CFLAGS="$CFLAGS -fstack-protector-all"]) +AX_CHECK_COMPILE_FLAG([-fno-strict-overflow], [CFLAGS="$CFLAGS -fno-strict-overflow"]) +AC_DEFINE([_FORTIFY_SOURCE], [2], [Enable fortification]) + +dnl XXX:TODO: Automatically determine the SSL library to use +dnl defaulting to OpenSSL for compatibility reasons +if test "$tcltls_ssl_lib" = 'auto'; then + tcltls_ssl_lib='openssl' +fi + +AC_MSG_CHECKING([which TLS library to use]) +TCLTLS_SSL_LIB="${tcltls_ssl_lib}" +AC_SUBST(TCLTLS_SSL_LIB) +AC_MSG_RESULT([$tcltls_ssl_lib]) + +dnl Manually rewrite libressl to OpenSSL since we use the +dnl compatibility interface +if test "$tcltls_ssl_lib" = "libressl"; then + tcltls_ssl_lib='openssl' +fi + +AS_CASE([$tcltls_ssl_lib], + [openssl], [ + TCLTLS_SSL_OPENSSL + AC_DEFINE([TCLTLS_SSL_OPENSSL], [1], [Define if the backend being used is OpenSSL (or the LibreSSL OpenSSL compatibility interface)]) + ], + [nss], [ + TCLTLS_SSL_LIBS="" + TCLTLS_SSL_CFLAGS="" + TCLTLS_SSL_CPPFLAGS="" + ], + [ + AC_MSG_ERROR([Unsupported SSL library: $tcltls_ssl_lib]) + ] +) +dnl Determine how to use this SSL library +AC_MSG_CHECKING([how to use $tcltls_ssl_lib]) +LIBS="${LIBS} ${TCLTLS_SSL_LIBS}" +CFLAGS="${CFLAGS} ${TCLTLS_SSL_CFLAGS}" +CPPFLAGS="${CPPFLAGS} ${TCLTLS_SSL_CPPFLAGS}" +AC_MSG_RESULT([$TCLTLS_SSL_CPPFLAGS $TCLTLS_SSL_CFLAGS $TCLTLS_SSL_LIBS]) + +dnl Sync the RPATH if requested +if test "$TCLEXT_BUILD" != 'static'; then + if test "$TCLEXT_TLS_STATIC_SSL" = 'yes'; then + DC_SYNC_RPATH([no]) + else + DC_SYNC_RPATH([yes]) + fi +fi + +dnl Produce output +AC_OUTPUT(Makefile pkgIndex.tcl) DELETED configure.in Index: configure.in ================================================================== --- configure.in +++ /dev/null @@ -1,175 +0,0 @@ -dnl Define ourselves -AC_INIT(tcltls, 1.7.3) - -dnl Checks for programs. -AC_PROG_CC -AC_PROG_MAKE_SET -AC_PROG_INSTALL -AC_GNU_SOURCE - -dnl Determine system information -DC_CHK_OS_INFO - -dnl Look for appropriate headers -AC_CHECK_HEADERS(unistd.h stdlib.h string.h strings.h) - -dnl Perform Tcl Extension required stuff -TCLEXT_INIT - -if test "$TCLEXT_BUILD" != 'static'; then - dnl Determine how to make shared objects - DC_GET_SHOBJFLAGS - - EXTENSION_TARGET="tcltls.${SHOBJEXT}" -else - AC_CHECK_TOOL([AR], [ar], [false]) - AC_CHECK_TOOL([RANLIB], [ranlib], [:]) - EXTENSION_TARGET="tcltls.${AREXT}" -fi -AC_SUBST(EXTENSION_TARGET) -AC_SUBST(TCLEXT_BUILD) - -dnl Determine what SSL library to link with -AC_ARG_WITH([ssl], AS_HELP_STRING([--with-ssl=], [name of ssl library to build against (openssl, libressl, nss, auto)]), [ - if test "$withval" = "no"; then - AC_MSG_ERROR([You may not specify --without-ssl]) - fi - - if test "$withval" = "yes"; then - AC_MSG_ERROR([If you specify --with-ssl then you must provide a value]) - fi - - tcltls_ssl_lib="$withval" -], [ - tcltls_ssl_lib='auto' -]) - -dnl Enable support for building the same library every time -tcltls_deterministic='false' -AC_ARG_ENABLE([deterministic], AS_HELP_STRING([--enable-deterministic], [enable deterministic parameters]), [ - if test "$enableval" = "yes"; then - tcltls_deterministic='true' - fi -]) -if test "$tcltls_deterministic" = 'true'; then - GEN_DH_PARAMS_ARGS='fallback' -else - 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]), [ - if test "$enableval" = "yes"; then - tcltls_ssl_ssl2='force' - fi -]) - -dnl ## SSLv3: Disabled by default -tcltls_ssl_ssl3='false' -AC_ARG_ENABLE([sslv3], AS_HELP_STRING([--enable-sslv3], [enable SSLv3 protocol]), [ - if test "$enableval" = "yes"; then - tcltls_ssl_ssl3='force' - 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 - 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 - 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 - tcltls_ssl_tls1_2='false' - fi -]) - -dnl Enable support for a debugging build -tcltls_debug='false' -AC_ARG_ENABLE([debug], AS_HELP_STRING([--enable-debug], [enable debugging parameters]), [ - if test "$enableval" = "yes"; then - tcltls_debug='true' - fi -]) -if test "$tcltls_debug" = 'true'; then - AC_DEFINE(TCLEXT_TCLTLS_DEBUG, [1], [Enable debugging build]) -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 statically link to the SSL library -TCLEXT_TLS_STATIC_SSL='no' -AC_ARG_ENABLE([static-ssl], AS_HELP_STRING([--enable-static-ssl], [enable statically linking to the specified SSL library]), [ - if test "$enableval" = 'yes'; then - TCLEXT_TLS_STATIC_SSL='yes' - fi -]) - -dnl XXX:TODO: Automatically determine the SSL library to use -dnl defaulting to OpenSSL for compatibility reasons -if test "$tcltls_ssl_lib" = 'auto'; then - tcltls_ssl_lib='openssl' -fi - -AC_MSG_CHECKING([which TLS library to use]) -TCLTLS_SSL_LIB="${tcltls_ssl_lib}" -AC_SUBST(TCLTLS_SSL_LIB) -AC_MSG_RESULT([$tcltls_ssl_lib]) - -dnl Manually rewrite libressl to OpenSSL since we use the -dnl compatibility interface -if test "$tcltls_ssl_lib" = "libressl"; then - tcltls_ssl_lib='openssl' -fi - -AS_CASE([$tcltls_ssl_lib], - [openssl], [ - TCLTLS_SSL_OPENSSL - AC_DEFINE([TCLTLS_SSL_OPENSSL], [1], [Define if the backend being used is OpenSSL (or the LibreSSL OpenSSL compatibility interface)]) - ], - [nss], [ - TCLTLS_SSL_LIBS="" - TCLTLS_SSL_CFLAGS="" - TCLTLS_SSL_CPPFLAGS="" - ], - [ - AC_MSG_ERROR([Unsupported SSL library: $tcltls_ssl_lib]) - ] -) -dnl Determine how to use this SSL library -AC_MSG_CHECKING([how to use $tcltls_ssl_lib]) -LIBS="${LIBS} ${TCLTLS_SSL_LIBS}" -CFLAGS="${CFLAGS} ${TCLTLS_SSL_CFLAGS}" -CPPFLAGS="${CPPFLAGS} ${TCLTLS_SSL_CPPFLAGS}" -AC_MSG_RESULT([$TCLTLS_SSL_CPPFLAGS $TCLTLS_SSL_CFLAGS $TCLTLS_SSL_LIBS]) - -dnl Sync the RPATH if requested -if test "$TCLEXT_BUILD" != 'static'; then - if test "$TCLEXT_TLS_STATIC_SSL" = 'yes'; then - DC_SYNC_RPATH([no]) - else - DC_SYNC_RPATH([yes]) - fi -fi - -dnl Produce output -AC_OUTPUT(Makefile pkgIndex.tcl) Index: gen_dh_params ================================================================== --- gen_dh_params +++ gen_dh_params @@ -100,10 +100,14 @@ gen_dh_params_fallback && exit 0 exit 1 fi +echo "*****************************" >&2 +echo "** Generating DH Primes. **" >&2 +echo "** This will take a while. **" >&2 +echo "*****************************" >&2 gen_dh_params_openssl && exit 0 gen_dh_params_remote && exit 0 gen_dh_params_fallback && exit 0 exit 1 Index: tests/all.tcl ================================================================== --- tests/all.tcl +++ tests/all.tcl @@ -7,11 +7,12 @@ # Copyright (c) 1998-2000 by Ajuba Solutions. # All rights reserved. # # RCS: @(#) $Id: all.tcl,v 1.5 2000/08/15 18:45:01 hobbs Exp $ -set auto_path [linsert $auto_path 0 [file normalize [file join [file dirname [info script]] ..]]] +#set auto_path [linsert $auto_path 0 [file normalize [file join [file dirname [info script]] ..]]] +set auto_path [linsert $auto_path 0 [file normalize [pwd]]] if {[lsearch [namespace children] ::tcltest] == -1} { package require tcltest namespace import ::tcltest::* } Index: tests/tlsIO.test ================================================================== --- tests/tlsIO.test +++ tests/tlsIO.test @@ -166,11 +166,11 @@ set remoteServerIP 127.0.0.1 set remoteFile [file join [pwd] remote.tcl] if {[catch {set remoteProcChan \ [open "|[list $::tcltest::tcltest $remoteFile \ -serverIsSilent -port $remoteServerPort \ - -address $remoteServerIP]" w+]} msg] == 0} { + -address $remoteServerIP] 2> /dev/null" w+]} msg] == 0} { after 1000 if {[catch {set commandSocket [tls::socket -cafile $caCert \ -certfile $clientCert -keyfile $clientKey \ $remoteServerIP $remoteServerPort]} msg] == 0} { fconfigure $commandSocket -translation crlf -buffering line @@ -320,11 +320,11 @@ after cancel $timer close $f puts $x } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f x if {[catch {tls::socket -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8828} msg]} { set x $msg } else { @@ -362,11 +362,11 @@ vwait x after cancel $timer close $f } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f x global port if {[catch {tls::socket -myport $port \ -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8829} sock]} { @@ -402,11 +402,11 @@ vwait x after cancel $timer close $f } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f x if {[catch {tls::socket -myaddr 127.0.0.1 \ -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8830} sock]} { set x $sock @@ -440,11 +440,11 @@ vwait x after cancel $timer close $f } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f x if {[catch {tls::socket -certfile $clientCert -cafile $caCert \ -keyfile $clientKey [info hostname] 8831} sock]} { set x $sock } else { @@ -477,11 +477,11 @@ vwait x after cancel $timer close $f } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f x if {[catch {tls::socket -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8832} sock]} { set x $sock } else { @@ -533,11 +533,11 @@ after cancel $timer close $f puts done } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f set s [tls::socket -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8834] fconfigure $s -buffering line -translation lf puts $s "hello abcdefghijklmnop" @@ -580,11 +580,11 @@ after cancel $timer close $f puts "done $i" } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f set s [tls::socket -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8835] fconfigure $s -buffering line catch { @@ -705,11 +705,11 @@ after cancel $timer close $f puts $x } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f x if {[catch {tls::socket 127.0.0.1 8828} msg]} { set x $msg } else { lappend x [gets $f] @@ -732,11 +732,11 @@ puts ready gets stdin close $f } close $f - set f [open "|[list $::tcltest::tcltest script]" r+] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] gets $f set x [list [catch {tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ -server accept 8828} msg] \ $msg] @@ -781,11 +781,11 @@ after cancel $t3 close $s puts $x } close $f - set f [open "|[list $::tcltest::tcltest script]" r+] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] set x [gets $f] set s1 [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ 127.0.0.1 8828] fconfigure $s1 -buffering line @@ -796,15 +796,15 @@ set s3 [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ 127.0.0.1 8828] fconfigure $s3 -buffering line for {set i 0} {$i < 100} {incr i} { - puts $s1 hello,s1 + puts $s1 hello,tlsIO-3.2,s1 gets $s1 - puts $s2 hello,s2 + puts $s2 hello,tlsIO-3.2,s2 gets $s2 - puts $s3 hello,s3 + puts $s3 hello,tlsIO-3.2,s3 gets $s3 } close $s1 close $s2 close $s3 @@ -832,15 +832,15 @@ close $s puts bye gets stdin } close $f - set p1 [open "|[list $::tcltest::tcltest script]" r+] + set p1 [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] fconfigure $p1 -buffering line - set p2 [open "|[list $::tcltest::tcltest script]" r+] + set p2 [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] fconfigure $p2 -buffering line - set p3 [open "|[list $::tcltest::tcltest script]" r+] + set p3 [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] fconfigure $p3 -buffering line proc accept {s a p} { fconfigure $s -buffering line fileevent $s readable [list echo $s] } @@ -930,11 +930,11 @@ package require tls gets stdin } puts $f [list tls::socket -cafile $caCert 127.0.0.1 8848] close $f - set f [open "|[list $::tcltest::tcltest script]" r+] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] proc bgerror args { global x set x $args } proc accept {s a p} {expr 10 / 0} @@ -968,11 +968,11 @@ set timer [after 10000 "set x timed_out"] vwait x after cancel $timer } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f set s [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ 127.0.0.1 8820] set p [fconfigure $s -peername] @@ -1001,11 +1001,11 @@ set timer [after 10000 "set x timed_out"] vwait x after cancel $timer } close $f - set f [open "|[list $::tcltest::tcltest script]" r] + set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] gets $f set s [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ 127.0.0.1 8821] set p [fconfigure $s -sockname] @@ -1451,15 +1451,15 @@ set s3 [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ $remoteServerIP 8836] fconfigure $s3 -buffering line for {set i 0} {$i < 100} {incr i} { - puts $s1 hello,s1 + puts $s1 hello,tlsIO-11.7,s1 gets $s1 - puts $s2 hello,s2 + puts $s2 hello,tlsIO-11.7,s2 gets $s2 - puts $s3 hello,s3 + puts $s3 hello,tlsIO-11.7,s3 gets $s3 } close $s1 close $s2 close $s3 @@ -2053,10 +2053,15 @@ [info hostname] 8831] fconfigure $c -blocking 0 puts $c a ; flush $c after 5000 [list set ::done timeout] vwait ::done + switch -exact -- $::done { + "handshake failed: wrong ssl version" { + set ::done "handshake failed: wrong version number" + } + } set ::done } {handshake failed: wrong version number} # cleanup if {[string match sock* $commandSocket] == 1} { Index: tls.c ================================================================== --- tls.c +++ tls.c @@ -63,11 +63,11 @@ static SSL_CTX *CTX_Init(State *statePtr, int proto, char *key, char *cert, char *CAdir, char *CAfile, char *ciphers, char *DHparams); -static int TlsLibInit(void); +static int TlsLibInit(int uninitialize); #define TLS_PROTO_SSL2 0x01 #define TLS_PROTO_SSL3 0x02 #define TLS_PROTO_TLS1 0x04 #define TLS_PROTO_TLS1_1 0x08 @@ -115,33 +115,42 @@ /* * Threaded operation requires locking callbacks * Based from /crypto/cryptlib.c of OpenSSL and NSOpenSSL. */ -#ifndef CRYPTO_NUM_LOCKS -#define CRYPTO_NUM_LOCKS 128 -#endif -static Tcl_Mutex locks[CRYPTO_NUM_LOCKS]; +static Tcl_Mutex *locks = NULL; +static int locksCount = 0; static Tcl_Mutex init_mx; -static void CryptoThreadLockCallback (int mode, int n, const char *file, int line); -static unsigned long CryptoThreadIdCallback (void); - -static void -CryptoThreadLockCallback(int mode, int n, const char *file, int line) -{ - if (mode & CRYPTO_LOCK) { - Tcl_MutexLock(&locks[n]); - } else { - Tcl_MutexUnlock(&locks[n]); - } -} - -static unsigned long -CryptoThreadIdCallback(void) -{ - return (unsigned long) Tcl_GetCurrentThread(); +void CryptoThreadLockCallback(int mode, int n, const char *file, int line) { + + if (mode & CRYPTO_LOCK) { + /* This debugging is turned off by default -- it's too noisy. */ + /* dprintf("Called to lock (n=%i of %i)", n, locksCount); */ + Tcl_MutexLock(&locks[n]); + } else { + /* dprintf("Called to unlock (n=%i of %i)", n, locksCount); */ + Tcl_MutexUnlock(&locks[n]); + } + + /* dprintf("Returning"); */ + + return; + file = file; + line = line; +} + +unsigned long CryptoThreadIdCallback(void) { + unsigned long ret; + + dprintf("Called"); + + ret = (unsigned long) Tcl_GetCurrentThread(); + + dprintf("Returning %lu", ret); + + return(ret); } #endif /* OPENSSL_THREADS */ #endif /* TCL_THREADS */ @@ -163,10 +172,12 @@ InfoCallback(CONST SSL *ssl, int where, int ret) { State *statePtr = (State*)SSL_get_app_data((SSL *)ssl); Tcl_Obj *cmdPtr; char *major; char *minor; + + dprintf("Called"); if (statePtr->callback == (Tcl_Obj*)NULL) return; cmdPtr = Tcl_DuplicateObj(statePtr->callback); @@ -349,10 +360,12 @@ void Tls_Error(State *statePtr, char *msg) { Tcl_Obj *cmdPtr; + dprintf("Called"); + if (msg && *msg) { Tcl_SetErrorCode(statePtr->interp, "SSL", msg, (char *)NULL); } else { msg = Tcl_GetStringFromObj(Tcl_GetObjResult(statePtr->interp), NULL); } @@ -407,19 +420,24 @@ */ static int PasswordCallback(char *buf, int size, int verify) { return -1; + buf = buf; + size = size; + verify = verify; } #else static int PasswordCallback(char *buf, int size, int verify, void *udata) { State *statePtr = (State *) udata; Tcl_Interp *interp = statePtr->interp; Tcl_Obj *cmdPtr; int result; + + dprintf("Called"); if (statePtr->password == NULL) { if (Tcl_EvalEx(interp, "tls::password", -1, TCL_EVAL_GLOBAL) == TCL_OK) { char *ret = (char *) Tcl_GetStringResult(interp); @@ -450,10 +468,11 @@ strncpy(buf, ret, (size_t) size); return (int)strlen(ret); } else { return -1; } + verify = verify; } #endif /* *------------------------------------------------------------------- @@ -488,10 +507,12 @@ SSL_CTX *ctx = NULL; SSL *ssl = NULL; STACK_OF(SSL_CIPHER) *sk; char *cp, buf[BUFSIZ]; int index, verbose = 0; + + dprintf("Called"); if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "protocol ?verbose?"); return TCL_ERROR; } @@ -583,10 +604,11 @@ SSL_free(ssl); SSL_CTX_free(ctx); Tcl_SetObjResult( interp, objPtr); return TCL_OK; + clientData = clientData; } /* *------------------------------------------------------------------- * @@ -612,10 +634,13 @@ 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"); return TCL_ERROR; } @@ -634,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); @@ -654,18 +679,21 @@ if (!errStr || *errStr == 0) { errStr = Tcl_PosixError(interp); } - Tcl_AppendResult(interp, "handshake failed: ", errStr, - (char *) NULL); + 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; } /* *------------------------------------------------------------------- * @@ -707,37 +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; + 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_SSL3) - int ssl3 = 0; -#else - int ssl3 = 1; +#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) - int tls1 = 0; -#else - int tls1 = 1; + tls1 = 0; #endif #if defined(NO_TLS1_1) - int tls1_1 = 0; -#else - int tls1_1 = 1; + tls1_1 = 0; #endif #if defined(NO_TLS1_2) - int tls1_2 = 0; -#else - int tls1_2 = 1; + tls1_2 = 0; #endif - int proto = 0; - int verify = 0, require = 0, request = 1; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel ?options?"); return TCL_ERROR; } @@ -864,11 +887,14 @@ * encryption not to get goofed up). * We only want to adjust the buffering in pre-v2 channels, where * each channel in the stack maintained its own buffers. */ Tcl_SetChannelOption(interp, chan, "-translation", "binary"); + Tcl_SetChannelOption(interp, chan, "-blocking", "true"); + dprintf("Consuming Tcl channel %s", Tcl_GetChannelName(chan)); statePtr->self = Tcl_StackChannel(interp, Tls_ChannelType(), (ClientData) statePtr, (TCL_READABLE | TCL_WRITABLE), chan); + dprintf("Created channel named %s", Tcl_GetChannelName(statePtr->self)); if (statePtr->self == (Tcl_Channel) NULL) { /* * No use of Tcl_EventuallyFree because no possible Tcl_Preserve. */ Tls_Free((char *) statePtr); @@ -908,11 +934,11 @@ SSL_set_verify(statePtr->ssl, verify, VerifyCallback); SSL_CTX_set_info_callback(statePtr->ctx, InfoCallback); /* Create Tcl_Channel BIO Handler */ - statePtr->p_bio = BIO_new_tcl(statePtr, BIO_CLOSE); + statePtr->p_bio = BIO_new_tcl(statePtr, BIO_NOCLOSE); statePtr->bio = BIO_new(BIO_f_ssl()); if (server) { statePtr->flags |= TLS_TCL_SERVER; SSL_set_accept_state(statePtr->ssl); @@ -923,13 +949,15 @@ BIO_set_ssl(statePtr->bio, statePtr->ssl, BIO_NOCLOSE); /* * End of SSL Init */ + dprintf("Returning %s", Tcl_GetChannelName(statePtr->self)); Tcl_SetResult(interp, (char *) Tcl_GetChannelName(statePtr->self), TCL_VOLATILE); return TCL_OK; + clientData = clientData; } /* *------------------------------------------------------------------- * @@ -953,10 +981,12 @@ int objc; Tcl_Obj *CONST objv[]; { Tcl_Channel chan; /* The channel to set a mode on. */ + dprintf("Called"); + if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel"); return TCL_ERROR; } @@ -979,10 +1009,11 @@ if (Tcl_UnstackChannel(interp, chan) == TCL_ERROR) { return TCL_ERROR; } return TCL_OK; + clientData = clientData; } /* *------------------------------------------------------------------- * @@ -1012,10 +1043,12 @@ SSL_CTX *ctx = NULL; Tcl_DString ds; Tcl_DString ds1; int off = 0; const SSL_METHOD *method; + + dprintf("Called"); if (!proto) { Tcl_AppendResult(interp, "no valid protocol selected", NULL); return (SSL_CTX *)0; } @@ -1262,10 +1295,12 @@ Tcl_Obj *objPtr; Tcl_Channel chan; char *channelName, *ciphers; int mode; + dprintf("Called"); + switch (objc) { case 2: channelName = Tcl_GetStringFromObj(objv[1], NULL); break; @@ -1318,10 +1353,11 @@ Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(SSL_get_cipher(statePtr->ssl), -1)); } Tcl_SetObjResult( interp, objPtr); return TCL_OK; + clientData = clientData; } /* *------------------------------------------------------------------- * @@ -1341,15 +1377,20 @@ Tcl_Interp *interp; int objc; Tcl_Obj *CONST objv[]; { Tcl_Obj *objPtr; + + dprintf("Called"); objPtr = Tcl_NewStringObj(OPENSSL_VERSION_TEXT, -1); Tcl_SetObjResult(interp, objPtr); return TCL_OK; + clientData = clientData; + objc = objc; + objv = objv; } /* *------------------------------------------------------------------- * @@ -1371,10 +1412,12 @@ Tcl_Obj *CONST objv[]; { static CONST84 char *commands [] = { "req", NULL }; enum command { C_REQ, C_DUMMY }; int cmd; + + dprintf("Called"); if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?"); return TCL_ERROR; } @@ -1508,10 +1551,11 @@ break; default: break; } return TCL_OK; + clientData = clientData; } /* *------------------------------------------------------------------- * @@ -1530,10 +1574,12 @@ */ void Tls_Free( char *blockPtr ) { State *statePtr = (State *)blockPtr; + + dprintf("Called"); Tls_Clean(statePtr); ckfree(blockPtr); } @@ -1553,17 +1599,16 @@ * Side effects: * Frees all the state * *------------------------------------------------------------------- */ -void -Tls_Clean(State *statePtr) -{ +void Tls_Clean(State *statePtr) { + dprintf("Called"); + /* * we're assuming here that we're single-threaded */ - if (statePtr->timer != (Tcl_TimerToken) NULL) { Tcl_DeleteTimerHandler(statePtr->timer); statePtr->timer = NULL; } @@ -1588,10 +1633,12 @@ } if (statePtr->password) { Tcl_DecrRefCount(statePtr->password); statePtr->password = NULL; } + + dprintf("Returning"); } /* *------------------------------------------------------------------- * @@ -1609,11 +1656,14 @@ */ int Tls_Init(Tcl_Interp *interp) { const char tlsTclInitScript[] = { #include "tls.tcl.h" + 0x00 }; + + dprintf("Called"); /* * We only support Tcl 8.4 or newer */ if ( @@ -1624,11 +1674,11 @@ #endif == NULL) { return TCL_ERROR; } - if (TlsLibInit() != TCL_OK) { + if (TlsLibInit(0) != TCL_OK) { Tcl_AppendResult(interp, "could not initialize SSL library", NULL); return TCL_ERROR; } Tcl_CreateObjCommand(interp, "tls::ciphers", CiphersObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); @@ -1664,10 +1714,11 @@ * *------------------------------------------------------* */ int Tls_SafeInit(Tcl_Interp *interp) { + dprintf("Called"); return(Tls_Init(interp)); } /* *------------------------------------------------------* @@ -1684,33 +1735,64 @@ * Result: * none * *------------------------------------------------------* */ -static int TlsLibInit(void) { +static int TlsLibInit(int uninitialize) { static int initialized = 0; int status = TCL_OK; +#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) + size_t num_locks; +#endif + + if (uninitialize) { + if (!initialized) { + dprintf("Asked to uninitialize, but we are not initialized"); + + return(TCL_OK); + } + + dprintf("Asked to uninitialize"); + +#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) + Tcl_MutexLock(&init_mx); + + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_id_callback(NULL); + + if (locks) { + free(locks); + locks = NULL; + locksCount = 0; + } +#endif + initialized = 0; + +#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) + Tcl_MutexUnlock(&init_mx); +#endif + + return(TCL_OK); + } if (initialized) { + dprintf("Called, but using cached value"); return(status); } + dprintf("Called"); + +#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) + Tcl_MutexLock(&init_mx); +#endif initialized = 1; #if defined(OPENSSL_THREADS) && defined(TCL_THREADS) - size_t num_locks; - - Tcl_MutexLock(&init_mx); -#endif - -#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) - /* should we consider allocating mutexes? */ num_locks = CRYPTO_num_locks(); - if (num_locks > CRYPTO_NUM_LOCKS) { - status = TCL_ERROR; - goto done; - } + locksCount = num_locks; + locks = malloc(sizeof(*locks) * num_locks); + memset(locks, 0, sizeof(*locks) * num_locks); CRYPTO_set_locking_callback(CryptoThreadLockCallback); CRYPTO_set_id_callback(CryptoThreadIdCallback); #endif @@ -1719,10 +1801,12 @@ goto done; } SSL_load_error_strings(); ERR_load_crypto_strings(); + + BIO_new_tcl(NULL, 0); #if 0 /* * XXX:TODO: Remove this code and replace it with a check * for enough entropy and do not try to create our own Index: tls.htm ================================================================== --- tls.htm +++ tls.htm @@ -203,16 +203,19 @@
Only available if the OpenSSL library the package is linked against supports the TLS hostname extension for 'Server Name Indication' (SNI). Use to name the logical host we are talking to and expecting a certificate for
-ssl2 bool
-
Enable use of SSL v2. (default: true - unless -DNO_PATENTS was specified in build)
+
Enable use of SSL v2. (default: false)
-ssl3 bool
-
Enable use of SSL v3. (default: true)
+
Enable use of SSL v3. (default: false)
-tls1 bool
-
Enable use of TLS v1. (default: false)
+
Enable use of TLS v1. (default: true)
+
-tls1.1 bool
+
Enable use of TLS v1.1 (default: true)
+
-tls1.2 bool
+
Enable use of TLS v1.2 (default: true)
tls::unimport channel
Index: tlsIO.c ================================================================== --- tlsIO.c +++ tlsIO.c @@ -158,21 +158,202 @@ * Side effects: * Closes the socket of the channel. * *------------------------------------------------------------------- */ -static int -TlsCloseProc(ClientData instanceData, /* The socket to close. */ - Tcl_Interp *interp) /* For error reporting - unused. */ -{ - State *statePtr = (State *) instanceData; - - dprintf("TlsCloseProc(%p)", (void *) statePtr); - - Tls_Clean(statePtr); - Tcl_EventuallyFree((ClientData)statePtr, Tls_Free); - return TCL_OK; +static int TlsCloseProc(ClientData instanceData, Tcl_Interp *interp) { + State *statePtr = (State *) instanceData; + + dprintf("TlsCloseProc(%p)", (void *) statePtr); + + Tls_Clean(statePtr); + Tcl_EventuallyFree((ClientData)statePtr, Tls_Free); + + dprintf("Returning TCL_OK"); + + 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); } /* *------------------------------------------------------------------- * @@ -190,87 +371,118 @@ * Reads input from the input device of the channel. * *------------------------------------------------------------------- */ -static int -TlsInputProc(ClientData instanceData, /* Socket state. */ - char *buf, /* Where to store data read. */ - int bufSize, /* How much space is available - * in the buffer? */ - int *errorCodePtr) /* Where to store error code. */ -{ - State *statePtr = (State *) instanceData; - int bytesRead; /* How many bytes were read? */ - - *errorCodePtr = 0; - - dprintf("BIO_read(%d)", bufSize); - - if (statePtr->flags & TLS_TCL_CALLBACK) { - /* don't process any bytes while verify callback is running */ - dprintf("Callback is running, reading 0 bytes"); - - bytesRead = 0; - goto input; - } - - if (!SSL_is_init_finished(statePtr->ssl)) { - dprintf("Calling Tls_WaitForConnect"); - bytesRead = Tls_WaitForConnect(statePtr, errorCodePtr); +static int TlsInputProc(ClientData instanceData, char *buf, int bufSize, int *errorCodePtr) { + unsigned long backingError; + State *statePtr = (State *) instanceData; + int bytesRead; + int tlsConnect; + int err; + + *errorCodePtr = 0; + + dprintf("BIO_read(%d)", bufSize); + + if (statePtr->flags & TLS_TCL_CALLBACK) { + /* don't process any bytes while verify callback is running */ + dprintf("Callback is running, reading 0 bytes"); + return(0); + } + + dprintf("Calling Tls_WaitForConnect"); + tlsConnect = Tls_WaitForConnect(statePtr, errorCodePtr, 0); + if (tlsConnect < 0) { + 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(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 + * returns -1 and intends EAGAIN, there is a leftover error, it will be + * misconstrued as an error, not EAGAIN. + * + * Alternatively, we may want to handle the <0 return codes from + * BIO_read specially (as advised in the RSA docs). TLS's lower level BIO + * functions play with the retry flags though, and this seems to work + * correctly. Similar fix in TlsOutputProc. - hobbs + */ + ERR_clear_error(); + bytesRead = BIO_read(statePtr->bio, buf, bufSize); + dprintf("BIO_read -> %d", bytesRead); + + err = SSL_get_error(statePtr->ssl, bytesRead); + +#if 0 if (bytesRead <= 0) { - dprintf("Got an error (bytesRead = %i)", bytesRead); - - if (*errorCodePtr == ECONNRESET) { - dprintf("Got connection reset"); - /* Soft EOF */ - *errorCodePtr = 0; - bytesRead = 0; - } - goto input; - } - } - - if (statePtr->flags & TLS_TCL_INIT) { - statePtr->flags &= ~(TLS_TCL_INIT); - } - /* - * We need to clear the SSL error stack now because we sometimes reach - * this function with leftover errors in the stack. If BIO_read - * returns -1 and intends EAGAIN, there is a leftover error, it will be - * misconstrued as an error, not EAGAIN. - * - * Alternatively, we may want to handle the <0 return codes from - * BIO_read specially (as advised in the RSA docs). TLS's lower level BIO - * functions play with the retry flags though, and this seems to work - * correctly. Similar fix in TlsOutputProc. - hobbs - */ - ERR_clear_error(); - bytesRead = BIO_read(statePtr->bio, buf, bufSize); - dprintf("BIO_read -> %d", bytesRead); - - if (bytesRead < 0) { - int err = SSL_get_error(statePtr->ssl, bytesRead); - - if (err == SSL_ERROR_SSL) { - Tls_Error(statePtr, TCLTLS_SSL_ERROR(statePtr->ssl, bytesRead)); - *errorCodePtr = ECONNABORTED; - } else if (BIO_should_retry(statePtr->bio)) { - dprintf("RE! "); - *errorCodePtr = EAGAIN; - } else { - *errorCodePtr = Tcl_GetErrno(); - if (*errorCodePtr == ECONNRESET) { - /* Soft EOF */ - *errorCodePtr = 0; - bytesRead = 0; - } - } - } - input: - dprintf("Input(%d) -> %d [%d]", bufSize, bytesRead, *errorCodePtr); - return bytesRead; + 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: + 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; + case SSL_ERROR_WANT_READ: + dprintf("Got SSL_ERROR_WANT_READ, mapping this to EAGAIN"); + bytesRead = -1; + *errorCodePtr = EAGAIN; + break; + default: + dprintf("Unknown error (err = %i), mapping to EOF", err); + *errorCodePtr = 0; + bytesRead = 0; + break; + } + + dprintf("Input(%d) -> %d [%d]", bufSize, bytesRead, *errorCodePtr); + return(bytesRead); } /* *------------------------------------------------------------------- * @@ -287,48 +499,61 @@ * Writes output on the output device of the channel. * *------------------------------------------------------------------- */ -static int -TlsOutputProc(ClientData instanceData, /* Socket state. */ - CONST char *buf, /* The data buffer. */ - int toWrite, /* How many bytes to write? */ - int *errorCodePtr) /* Where to store error code. */ -{ - State *statePtr = (State *) instanceData; - int written, err; - - *errorCodePtr = 0; - - dprintf("BIO_write(%p, %d)", (void *) statePtr, toWrite); - - if (statePtr->flags & TLS_TCL_CALLBACK) { - /* don't process any bytes while verify callback is running */ - written = -1; - *errorCodePtr = EAGAIN; - goto output; - } - - if (!SSL_is_init_finished(statePtr->ssl)) { - dprintf("Calling Tls_WaitForConnect"); - written = Tls_WaitForConnect(statePtr, errorCodePtr); - if (written <= 0) { - dprintf("Tls_WaitForConnect returned %i (err = %i)", written, *errorCodePtr); - - goto output; - } - } - if (statePtr->flags & TLS_TCL_INIT) { - statePtr->flags &= ~(TLS_TCL_INIT); - } - if (toWrite == 0) { - dprintf("zero-write"); - BIO_flush(statePtr->bio); - written = 0; - goto output; - } else { +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); + + if (statePtr->flags & TLS_TCL_CALLBACK) { + dprintf("Don't process output while callbacks are running") + written = -1; + *errorCodePtr = EAGAIN; + return(-1); + } + + dprintf("Calling Tls_WaitForConnect"); + 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(written); + } + + if (toWrite == 0) { + dprintf("zero-write"); + err = BIO_flush(statePtr->bio); + + if (err <= 0) { + dprintf("Flushing failed"); + + *errorCodePtr = EIO; + written = 0; + return(-1); + } + + written = 0; + *errorCodePtr = 0; + return(0); + } + /* * We need to clear the SSL error stack now because we sometimes reach * this function with leftover errors in the stack. If BIO_write * returns -1 and intends EAGAIN, there is a leftover error, it will be * misconstrued as an error, not EAGAIN. @@ -338,52 +563,65 @@ * BIO functions play with the retry flags though, and this seems to * work correctly. Similar fix in TlsInputProc. - hobbs */ ERR_clear_error(); written = BIO_write(statePtr->bio, buf, toWrite); - dprintf("BIO_write(%p, %d) -> [%d]", - (void *) statePtr, toWrite, written); - } - if (written <= 0) { - switch ((err = SSL_get_error(statePtr->ssl, written))) { - case SSL_ERROR_NONE: - if (written < 0) { - written = 0; - } - break; - case SSL_ERROR_WANT_WRITE: - dprintf(" write W BLOCK"); - break; - case SSL_ERROR_WANT_READ: - dprintf(" write R BLOCK"); - break; - case SSL_ERROR_WANT_X509_LOOKUP: - dprintf(" write X BLOCK"); - break; - case SSL_ERROR_ZERO_RETURN: - dprintf(" closed"); - written = 0; - break; - case SSL_ERROR_SYSCALL: - *errorCodePtr = Tcl_GetErrno(); - dprintf(" [%d] syscall errr: %d", - written, *errorCodePtr); - written = -1; - break; - case SSL_ERROR_SSL: - Tls_Error(statePtr, TCLTLS_SSL_ERROR(statePtr->ssl, written)); - *errorCodePtr = ECONNABORTED; - written = -1; - break; - default: - dprintf(" unknown err: %d", err); - break; - } - } - output: - dprintf("Output(%d) -> %d", toWrite, written); - return written; + dprintf("BIO_write(%p, %d) -> [%d]", (void *) statePtr, toWrite, written); + + err = SSL_get_error(statePtr->ssl, written); + switch (err) { + case SSL_ERROR_NONE: + if (written < 0) { + written = 0; + } + break; + case SSL_ERROR_WANT_WRITE: + dprintf("Got SSL_ERROR_WANT_WRITE, mapping it to EAGAIN"); + *errorCodePtr = EAGAIN; + written = -1; + break; + case SSL_ERROR_WANT_READ: + dprintf(" write R BLOCK"); + break; + case SSL_ERROR_WANT_X509_LOOKUP: + dprintf(" write X BLOCK"); + break; + case SSL_ERROR_ZERO_RETURN: + dprintf(" closed"); + written = 0; + *errorCodePtr = 0; + break; + case SSL_ERROR_SYSCALL: + 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; + break; + default: + dprintf(" unknown err: %d", err); + break; + } + + dprintf("Output(%d) -> %d", toWrite, written); + return(written); } /* *------------------------------------------------------------------- * @@ -412,11 +650,11 @@ Tcl_DString *dsPtr) /* Where to store the computed value * initialized by caller. */ { State *statePtr = (State *) instanceData; - Tcl_Channel downChan = Tls_GetParent(statePtr); + Tcl_Channel downChan = Tls_GetParent(statePtr, TLS_TCL_FASTPATH); Tcl_DriverGetOptionProc *getOptionProc; getOptionProc = Tcl_ChannelGetOptionProc(Tcl_GetChannelType(downChan)); if (getOptionProc != NULL) { return (*getOptionProc)(Tcl_GetChannelInstanceData(downChan), interp, optionName, dsPtr); @@ -460,11 +698,29 @@ dprintf("TlsWatchProc(0x%x)", mask); /* Pretend to be dead as long as the verify callback is running. * Otherwise that callback could be invoked recursively. */ - if (statePtr->flags & TLS_TCL_CALLBACK) { return; } + if (statePtr->flags & TLS_TCL_CALLBACK) { + dprintf("Callback is on-going, doing nothing"); + return; + } + + dprintFlags(statePtr); + + downChan = Tls_GetParent(statePtr, TLS_TCL_FASTPATH); + + if (statePtr->flags & TLS_TCL_HANDSHAKE_FAILED) { + dprintf("Asked to watch a socket with a failed handshake -- nothing can happen here"); + + dprintf("Unregistering interest in the lower channel"); + (Tcl_GetChannelType(downChan))->watchProc(Tcl_GetChannelInstanceData(downChan), 0); + + statePtr->watchMask = 0; + + return; + } statePtr->watchMask = mask; /* No channel handlers any more. We will be notified automatically * about events on the channel below via a call to our @@ -472,28 +728,31 @@ * We are allowed to add additional 'interest' to the mask if we want * to. But this transformation has no such interest. It just passes * the request down, unchanged. */ - downChan = Tls_GetParent(statePtr); + dprintf("Registering our interest in the lower channel (chan=%p)", (void *) downChan); (Tcl_GetChannelType(downChan)) ->watchProc(Tcl_GetChannelInstanceData(downChan), mask); /* * Management of the internal timer. */ if (statePtr->timer != (Tcl_TimerToken) NULL) { + dprintf("A timer was found, deleting it"); Tcl_DeleteTimerHandler(statePtr->timer); statePtr->timer = (Tcl_TimerToken) NULL; } + if ((mask & TCL_READABLE) && Tcl_InputBuffered(statePtr->self) > 0) { /* * There is interest in readable events and we actually have * data waiting, so generate a timer to flush that. */ + dprintf("Creating a new timer since data appears to be waiting"); statePtr->timer = Tcl_CreateTimerHandler(TLS_TCL_DELAY, TlsChannelHandlerTimer, (ClientData) statePtr); } } @@ -511,18 +770,14 @@ * Side effects: * None. * *------------------------------------------------------------------- */ -static int -TlsGetHandleProc(ClientData instanceData, /* The socket state. */ - int direction, /* Which Tcl_File to retrieve? */ - ClientData *handlePtr) /* Where to store the handle. */ -{ - State *statePtr = (State *) instanceData; - - return Tcl_GetChannelHandle(Tls_GetParent(statePtr), direction, handlePtr); +static int TlsGetHandleProc(ClientData instanceData, int direction, ClientData *handlePtr) { + State *statePtr = (State *) instanceData; + + return(Tcl_GetChannelHandle(Tls_GetParent(statePtr, TLS_TCL_FASTPATH), direction, handlePtr)); } /* *------------------------------------------------------------------- * @@ -538,59 +793,51 @@ * May process the incoming event by itself. * *------------------------------------------------------------------- */ -static int -TlsNotifyProc(instanceData, mask) - ClientData instanceData; /* The state of the notified transformation */ - int mask; /* The mask of occuring events */ -{ - State *statePtr = (State *) instanceData; - - /* - * An event occured in the underlying channel. This - * transformation doesn't process such events thus returns the - * incoming mask unchanged. - */ - - if (statePtr->timer != (Tcl_TimerToken) NULL) { - /* - * Delete an existing timer. It was not fired, yet we are - * here, so the channel below generated such an event and we - * don't have to. The renewal of the interest after the - * execution of channel handlers will eventually cause us to - * recreate the timer (in WatchProc). - */ - - Tcl_DeleteTimerHandler(statePtr->timer); - statePtr->timer = (Tcl_TimerToken) NULL; - } - - if (statePtr->flags & TLS_TCL_CALLBACK) { - dprintf("Returning 0 due to callback"); - return 0; - } - - if ((statePtr->flags & TLS_TCL_INIT) && !SSL_is_init_finished(statePtr->ssl)) { - int errorCode = 0; - - dprintf("Calling Tls_WaitForConnect"); - if (Tls_WaitForConnect(statePtr, &errorCode) <= 0) { - if (errorCode == EAGAIN) { - dprintf("Async flag could be set (didn't check) and errorCode == EAGAIN: Returning 0"); - - return 0; - } - - dprintf("Tls_WaitForConnect returned an error"); - } - } - - dprintf("Returning %i", mask); - - return mask; +static int TlsNotifyProc(ClientData instanceData, int mask) { + State *statePtr = (State *) instanceData; + int errorCode; + + /* + * An event occured in the underlying channel. This + * transformation doesn't process such events thus returns the + * incoming mask unchanged. + */ + if (statePtr->timer != (Tcl_TimerToken) NULL) { + /* + * Delete an existing timer. It was not fired, yet we are + * here, so the channel below generated such an event and we + * don't have to. The renewal of the interest after the + * execution of channel handlers will eventually cause us to + * recreate the timer (in WatchProc). + */ + Tcl_DeleteTimerHandler(statePtr->timer); + statePtr->timer = (Tcl_TimerToken) NULL; + } + + if (statePtr->flags & TLS_TCL_CALLBACK) { + dprintf("Returning 0 due to callback"); + return 0; + } + + dprintf("Calling Tls_WaitForConnect"); + 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; + } + + dprintf("Tls_WaitForConnect returned an error"); + } + + dprintf("Returning %i", mask); + + return(mask); } #if 0 /* *------------------------------------------------------* @@ -690,130 +937,43 @@ * None. * *------------------------------------------------------* */ -static void -TlsChannelHandlerTimer (clientData) -ClientData clientData; /* Transformation to query */ -{ - State *statePtr = (State *) clientData; - int mask = 0; - - statePtr->timer = (Tcl_TimerToken) NULL; - - if (BIO_wpending(statePtr->bio)) { - mask |= TCL_WRITABLE; - } - if (BIO_pending(statePtr->bio)) { - mask |= TCL_READABLE; - } - Tcl_NotifyChannel(statePtr->self, mask); -} - -/* - *------------------------------------------------------* - * - * Tls_WaitForConnect -- - * - * Sideeffects: - * Issues SSL_accept or SSL_connect - * - * Result: - * None. - * - *------------------------------------------------------* - */ -int Tls_WaitForConnect(State *statePtr, int *errorCodePtr) { - int err; - - dprintf("WaitForConnect(%p)", (void *) statePtr); - - 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); - } - - /*SSL_write(statePtr->ssl, (char*)&err, 0); HACK!!! */ - if (err > 0) { - dprintf("That seems to have gone okay"); - BIO_flush(statePtr->bio); - } else { - int rc = SSL_get_error(statePtr->ssl, err); - - dprintf("Got error: %i (rc = %i)", err, rc); - - if (rc == SSL_ERROR_SSL) { - Tls_Error(statePtr, - (char *)ERR_reason_error_string(ERR_get_error())); - statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; - *errorCodePtr = ECONNABORTED; - return -1; - } else if (BIO_should_retry(statePtr->bio)) { - if (statePtr->flags & TLS_TCL_ASYNC) { - dprintf("E! "); - *errorCodePtr = EAGAIN; - return -1; - } else { - continue; - } - } else if (err <= 0) { - if (SSL_in_init(statePtr->ssl)) { - dprintf("SSL_in_init() is true"); - } - - if (Tcl_Eof(statePtr->self)) { - dprintf("Error = 0 and EOF is set"); - - if (rc != SSL_ERROR_SYSCALL) { - dprintf("Error from some reason other than our BIO, returning 0"); - return 0; - } - } - dprintf("CR! "); - statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; - *errorCodePtr = ECONNRESET; - return -1; - } - if (statePtr->flags & TLS_TCL_SERVER) { - err = SSL_get_verify_result(statePtr->ssl); - if (err != X509_V_OK) { - Tls_Error(statePtr, - (char *)X509_verify_cert_error_string(err)); - statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; - *errorCodePtr = ECONNABORTED; - return -1; - } - } - *errorCodePtr = Tcl_GetErrno(); - dprintf("ERR(%d, %d) ", rc, *errorCodePtr); - return -1; - } - dprintf("R0! "); - return 1; - } -} - -Tcl_Channel Tls_GetParent(State *statePtr) { - dprintf("Requested to get parent of channel %p", statePtr->self); +static void TlsChannelHandlerTimer(ClientData clientData) { + State *statePtr = (State *) clientData; + int mask = 0; + + dprintf("Called"); + + statePtr->timer = (Tcl_TimerToken) NULL; + + if (BIO_wpending(statePtr->bio)) { + dprintf("[chan=%p] BIO writable", statePtr->self); + + mask |= TCL_WRITABLE; + } + + if (BIO_pending(statePtr->bio)) { + dprintf("[chan=%p] BIO readable", statePtr->self); + + mask |= TCL_READABLE; + } + + dprintf("Notifying ourselves"); + Tcl_NotifyChannel(statePtr->self, mask); + + dprintf("Returning"); + + return; +} + +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"); + return(NULL); + } return(Tcl_GetStackedChannel(statePtr->self)); } Index: tlsInt.h ================================================================== --- tlsInt.h +++ tlsInt.h @@ -19,10 +19,11 @@ #define _TLSINT_H #include "tls.h" #include #include +#include #ifdef __WIN32__ #define WIN32_LEAN_AND_MEAN #include #include /* OpenSSL needs this on Windows */ @@ -63,13 +64,49 @@ #ifndef ECONNRESET #define ECONNRESET 131 /* Connection reset by peer */ #endif #ifdef TCLEXT_TCLTLS_DEBUG -#define dprintf(...) { fprintf(stderr, "%s:%i:", __func__, __LINE__); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } +#include +#define dprintf(...) { \ + char dprintfBuffer[8192], *dprintfBuffer_p; \ + dprintfBuffer_p = &dprintfBuffer[0]; \ + dprintfBuffer_p += sprintf(dprintfBuffer_p, "%s:%i:%s():", __FILE__, __LINE__, __func__); \ + dprintfBuffer_p += sprintf(dprintfBuffer_p, __VA_ARGS__); \ + fprintf(stderr, "%s\n", dprintfBuffer); \ + } +#define dprintBuffer(bufferName, bufferLength) { \ + int dprintBufferIdx; \ + unsigned char dprintBufferChar; \ + fprintf(stderr, "%s:%i:%s():%s[%llu]={", __FILE__, __LINE__, __func__, #bufferName, (unsigned long long) bufferLength); \ + for (dprintBufferIdx = 0; dprintBufferIdx < bufferLength; dprintBufferIdx++) { \ + dprintBufferChar = bufferName[dprintBufferIdx]; \ + if (isalpha(dprintBufferChar) || isdigit(dprintBufferChar)) { \ + fprintf(stderr, "'%c' ", dprintBufferChar); \ + } else { \ + fprintf(stderr, "%02x ", (unsigned int) dprintBufferChar); \ + }; \ + }; \ + fprintf(stderr, "}\n"); \ + } +#define dprintFlags(statePtr) { \ + char dprintfBuffer[8192], *dprintfBuffer_p; \ + dprintfBuffer_p = &dprintfBuffer[0]; \ + dprintfBuffer_p += sprintf(dprintfBuffer_p, "%s:%i:%s():%s->flags=0", __FILE__, __LINE__, __func__, #statePtr); \ + if (((statePtr)->flags & TLS_TCL_ASYNC) == TLS_TCL_ASYNC) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_ASYNC"); }; \ + if (((statePtr)->flags & TLS_TCL_SERVER) == TLS_TCL_SERVER) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_SERVER"); }; \ + if (((statePtr)->flags & TLS_TCL_INIT) == TLS_TCL_INIT) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_INIT"); }; \ + if (((statePtr)->flags & TLS_TCL_DEBUG) == TLS_TCL_DEBUG) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_DEBUG"); }; \ + if (((statePtr)->flags & TLS_TCL_CALLBACK) == TLS_TCL_CALLBACK) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_CALLBACK"); }; \ + if (((statePtr)->flags & TLS_TCL_HANDSHAKE_FAILED) == TLS_TCL_HANDSHAKE_FAILED) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_HANDSHAKE_FAILED"); }; \ + if (((statePtr)->flags & TLS_TCL_FASTPATH) == TLS_TCL_FASTPATH) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_FASTPATH"); }; \ + fprintf(stderr, "%s\n", dprintfBuffer); \ + } #else #define dprintf(...) if (0) { fprintf(stderr, __VA_ARGS__); } +#define dprintBuffer(bufferName, bufferLength) /**/ +#define dprintFlags(statePtr) /**/ #endif #define TCLTLS_SSL_ERROR(ssl,err) ((char*)ERR_reason_error_string((unsigned long)SSL_get_error((ssl),(err)))) /* * OpenSSL BIO Routines @@ -86,11 +123,11 @@ #define TLS_TCL_CALLBACK (1<<4) /* In a callback, prevent update * looping problem. [Bug 1652380] */ #define TLS_TCL_HANDSHAKE_FAILED (1<<5) /* Set on handshake failures and once * set, all further I/O will result * in ECONNABORTED errors. */ - +#define TLS_TCL_FASTPATH (1<<6) /* The parent channel is being used directly by the SSL library */ #define TLS_TCL_DELAY (5) /* * This structure describes the per-instance state * of an ssl channel. @@ -126,16 +163,18 @@ /* * Forward declarations */ Tcl_ChannelType *Tls_ChannelType(void); -Tcl_Channel Tls_GetParent(State *statePtr); +Tcl_Channel Tls_GetParent(State *statePtr, int maskFlags); 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))) #endif /* _TLSINT_H */ Index: tlsX509.c ================================================================== --- tlsX509.c +++ tlsX509.c @@ -119,29 +119,29 @@ X509_NAME_print_ex(bio, X509_get_subject_name(cert), 0, flags); n = BIO_read(bio, subject, min(BIO_pending(bio), BUFSIZ - 1)); n = max(n, 0); subject[n] = 0; - BIO_flush(bio); + (void)BIO_flush(bio); X509_NAME_print_ex(bio, X509_get_issuer_name(cert), 0, flags); n = BIO_read(bio, issuer, min(BIO_pending(bio), BUFSIZ - 1)); n = max(n, 0); issuer[n] = 0; - BIO_flush(bio); + (void)BIO_flush(bio); i2a_ASN1_INTEGER(bio, X509_get_serialNumber(cert)); n = BIO_read(bio, serial, min(BIO_pending(bio), BUFSIZ - 1)); n = max(n, 0); serial[n] = 0; - BIO_flush(bio); + (void)BIO_flush(bio); if (PEM_write_bio_X509(bio, cert)) { n = BIO_read(bio, certStr, min(BIO_pending(bio), BUFSIZ - 1)); n = max(n, 0); certStr[n] = 0; - BIO_flush(bio); + (void)BIO_flush(bio); } BIO_free(bio); } Index: tls_openssl_bio.c ================================================================== --- tls_openssl_bio.c +++ tls_openssl_bio.c @@ -39,10 +39,18 @@ static int BioFree _ANSI_ARGS_((BIO *h)); BIO *BIO_new_tcl(State *statePtr, int flags) { BIO *bio; static BIO_METHOD *BioMethods = NULL; +#ifdef TCLTLS_SSL_USE_FASTPATH + Tcl_Channel parentChannel; + const Tcl_ChannelType *parentChannelType; + void *parentChannelFdIn_p, *parentChannelFdOut_p; + int parentChannelFdIn, parentChannelFdOut, parentChannelFd; + int validParentChannelFd; + int tclGetChannelHandleRet; +#endif dprintf("BIO_new_tcl() called"); if (BioMethods == NULL) { BioMethods = BIO_meth_new(BIO_TYPE_TCL, "tcl"); @@ -52,82 +60,155 @@ BIO_meth_set_ctrl(BioMethods, BioCtrl); BIO_meth_set_create(BioMethods, BioNew); BIO_meth_set_destroy(BioMethods, BioFree); } - bio = BIO_new(BioMethods); + if (statePtr == NULL) { + dprintf("Asked to setup a NULL state, just creating the initial configuration"); + + return(NULL); + } + +#ifdef TCLTLS_SSL_USE_FASTPATH + /* + * If the channel can be mapped back to a file descriptor, just use the file descriptor + * with the SSL library since it will likely be optimized for this. + */ + parentChannel = Tls_GetParent(statePtr, 0); + parentChannelType = Tcl_GetChannelType(parentChannel); + + validParentChannelFd = 0; + if (strcmp(parentChannelType->typeName, "tcp") == 0) { + tclGetChannelHandleRet = Tcl_GetChannelHandle(parentChannel, TCL_READABLE, (ClientData) &parentChannelFdIn_p); + if (tclGetChannelHandleRet == TCL_OK) { + tclGetChannelHandleRet = Tcl_GetChannelHandle(parentChannel, TCL_WRITABLE, (ClientData) &parentChannelFdOut_p); + if (tclGetChannelHandleRet == TCL_OK) { + parentChannelFdIn = PTR2INT(parentChannelFdIn_p); + parentChannelFdOut = PTR2INT(parentChannelFdOut_p); + if (parentChannelFdIn == parentChannelFdOut) { + parentChannelFd = parentChannelFdIn; + validParentChannelFd = 1; + } + } + } + } + + if (validParentChannelFd) { + dprintf("We found a shortcut, this channel is backed by a socket: %i", parentChannelFdIn); + bio = BIO_new_socket(parentChannelFd, flags); + statePtr->flags |= TLS_TCL_FASTPATH; + return(bio); + } + + dprintf("Falling back to Tcl I/O for this channel"); +#endif + bio = BIO_new(BioMethods); BIO_set_data(bio, statePtr); - BIO_set_init(bio, 1); BIO_set_shutdown(bio, flags); + BIO_set_init(bio, 1); return(bio); } static int BioWrite(BIO *bio, CONST char *buf, int bufLen) { Tcl_Channel chan; int ret; + int tclEofChan, tclErrno; - chan = Tls_GetParent((State *) BIO_get_data(bio)); + chan = Tls_GetParent((State *) BIO_get_data(bio), 0); - dprintf("BioWrite(%p, , %d) [%p]", (void *) bio, bufLen, (void *) chan); + dprintf("[chan=%p] BioWrite(%p, , %d)", (void *)chan, (void *) bio, bufLen); ret = Tcl_WriteRaw(chan, buf, bufLen); - dprintf("[%p] BioWrite(%d) -> %d [%d.%d]", (void *) chan, bufLen, ret, Tcl_Eof(chan), Tcl_GetErrno()); + tclEofChan = Tcl_Eof(chan); + tclErrno = Tcl_GetErrno(); + + dprintf("[chan=%p] BioWrite(%d) -> %d [tclEof=%d; tclErrno=%d]", (void *) chan, bufLen, ret, tclEofChan, Tcl_GetErrno()); BIO_clear_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY); - if (ret == 0) { - if (!Tcl_Eof(chan)) { - BIO_set_retry_write(bio); - ret = -1; - } - } - - if (BIO_should_read(bio)) { + if (tclEofChan && ret <= 0) { + dprintf("Got %i from Tcl_WriteRaw, and EOF is set; ret = -1", ret); + Tcl_SetErrno(ECONNRESET); + ret = -1; + } else if (ret == 0) { + dprintf("Got 0 from Tcl_WriteRaw, and EOF is not set; ret = 0"); + dprintf("Setting retry read flag"); BIO_set_retry_read(bio); + } else if (ret < 0) { + dprintf("We got some kind of I/O error"); + + if (tclErrno == EAGAIN) { + dprintf("It's EAGAIN"); + } else { + dprintf("It's an unepxected error: %s/%i", Tcl_ErrnoMsg(tclErrno), tclErrno); + } + } else { + dprintf("Successfully wrote some data"); + } + + if (ret != -1 || (ret == -1 && tclErrno == EAGAIN)) { + if (BIO_should_read(bio)) { + dprintf("Setting should retry read flag"); + + BIO_set_retry_read(bio); + } } return(ret); } static int BioRead(BIO *bio, char *buf, int bufLen) { Tcl_Channel chan; int ret = 0; - int tclEofChan; + int tclEofChan, tclErrno; - chan = Tls_GetParent((State *) BIO_get_data(bio)); + chan = Tls_GetParent((State *) BIO_get_data(bio), 0); - dprintf("BioRead(%p, , %d) [%p]", (void *) bio, bufLen, (void *) chan); + dprintf("[chan=%p] BioRead(%p, , %d)", (void *) chan, (void *) bio, bufLen); if (buf == NULL) { return 0; } ret = Tcl_ReadRaw(chan, buf, bufLen); tclEofChan = Tcl_Eof(chan); + tclErrno = Tcl_GetErrno(); - dprintf("[%p] BioRead(%d) -> %d [tclEof=%d; tclErrno=%d]", (void *) chan, bufLen, ret, tclEofChan, Tcl_GetErrno()); + dprintf("[chan=%p] BioRead(%d) -> %d [tclEof=%d; tclErrno=%d]", (void *) chan, bufLen, ret, tclEofChan, tclErrno); BIO_clear_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY); - if (ret == 0) { - if (!tclEofChan) { - dprintf("Got 0 from Tcl_Read or Tcl_ReadRaw, and EOF is not set -- ret == -1 now"); - BIO_set_retry_read(bio); - ret = -1; + if (tclEofChan && ret <= 0) { + dprintf("Got %i from Tcl_Read or Tcl_ReadRaw, and EOF is set; ret = -1", ret); + Tcl_SetErrno(ECONNRESET); + ret = -1; + } else if (ret == 0) { + dprintf("Got 0 from Tcl_Read or Tcl_ReadRaw, and EOF is not set; ret = 0"); + dprintf("Setting retry read flag"); + BIO_set_retry_read(bio); + } else if (ret < 0) { + dprintf("We got some kind of I/O error"); + + if (tclErrno == EAGAIN) { + dprintf("It's EAGAIN"); } else { - dprintf("Got 0 from Tcl_Read or Tcl_ReadRaw, and EOF is set"); + dprintf("It's an unepxected error: %s/%i", Tcl_ErrnoMsg(tclErrno), tclErrno); } } else { - dprintf("Got non-zero from Tcl_Read or Tcl_ReadRaw; ret == %i", ret); + dprintf("Successfully read some data"); } - if (BIO_should_write(bio)) { - BIO_set_retry_write(bio); + if (ret != -1 || (ret == -1 && tclErrno == EAGAIN)) { + if (BIO_should_write(bio)) { + dprintf("Setting should retry write flag"); + + BIO_set_retry_write(bio); + } } dprintf("BioRead(%p, , %d) [%p] returning %i", (void *) bio, bufLen, (void *) chan, ret); return(ret); @@ -141,11 +222,11 @@ static long BioCtrl(BIO *bio, int cmd, long num, void *ptr) { Tcl_Channel chan; long ret = 1; - chan = Tls_GetParent((State *) BIO_get_data(bio)); + chan = Tls_GetParent((State *) BIO_get_data(bio), 0); dprintf("BioCtrl(%p, 0x%x, 0x%x, %p)", (void *) bio, (unsigned int) cmd, (unsigned int) num, (void *) ptr); switch (cmd) { case BIO_CTRL_RESET: @@ -198,11 +279,11 @@ ret = ((Tcl_WriteRaw(chan, "", 0) >= 0) ? 1 : -1); dprintf("BIO_CTRL_FLUSH returning value %li", ret); break; default: dprintf("Got unknown control command (%i)", cmd); - ret = 0; + ret = -2; break; } return(ret); }