Allow building against GnuTLS (for TCP) and GnuTLS (for DTLS) simultaneously
authorDavid Woodhouse <David.Woodhouse@intel.com>
Mon, 11 Jun 2012 12:25:58 +0000 (13:25 +0100)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Mon, 11 Jun 2012 12:25:58 +0000 (13:25 +0100)
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
Makefile.am
configure.ac
dtls.c
openconnect-internal.h
openssl.c
ssl.c

index 1bf56ea..934ab7c 100644 (file)
@@ -14,8 +14,8 @@ man8_MANS = openconnect.8
 AM_CPPFLAGS = -DLOCALEDIR="\"$(localedir)\""
 openconnect_SOURCES = xml.c main.c dtls.c cstp.c mainloop.c tun.c
 
-openconnect_CFLAGS = $(SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS)
-openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL)
+openconnect_CFLAGS = $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS)
+openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL)
 
 library_srcs = ssl.c http.c auth.c library.c compat.c @SSL_LIBRARY@.c
 libopenconnect_la_SOURCES = version.c $(library_srcs)
index ed464b6..1ab7e41 100644 (file)
@@ -179,6 +179,17 @@ if test "$USE_NLS" = "yes"; then
 fi
 AM_CONDITIONAL(USE_NLS, [test "$USE_NLS" = "yes"])
 
+# We will use GnuTLS if it's requested, and if GnuTLS doesn't have DTLS
+# support then we'll *also* use OpenSSL for that, but it appears *only*
+# only in the openconnect executable and not the library (hence shouldn't
+# be a problem for GPL'd programs using libopenconnect).
+#
+# If built with --with-gnutls --without-openssl then we'll even eschew
+# OpenSSL for DTLS support and will build without any DTLS support at all
+# if GnuTLS cannot manage.
+#
+# The default (for now) is to use OpenSSL for everything.
+
 AC_ARG_WITH([gnutls],
        AS_HELP_STRING([--with-gnutls], 
                       [Use GnuTLS instead of OpenSSL (EXPERIMENTAL)]))
@@ -187,24 +198,46 @@ AC_ARG_WITH([openssl],
                            [Location of OpenSSL build dir]))
 ssl_library=
 
-if test "$with_gnutls" = "yes" || test "$with_gnutls" = "shibboleet"; then
-    if test "$with_openssl" != "no" && test "$with_openssl" != ""; then
-       AC_MSG_ERROR([Cannot use both OpenSSL and GnuTLS simultaneously])
-    fi
+if test "$with_gnutls" = "yes"; then
     PKG_CHECK_MODULES(GNUTLS, gnutls)
     if ! $PKG_CONFIG --atleast-version=2.12.16 gnutls; then
        AC_MSG_ERROR([Your GnuTLS is too old. At least v2.12.16 is required])
     fi
-    with_openssl=no
-    ssl_library=gnutls
     oldlibs="$LIBS"
     LIBS="$LIBS $GNUTLS_LIBS"
     AC_CHECK_FUNC(gnutls_certificate_set_x509_system_trust,
                 [AC_DEFINE(HAVE_GNUTLS_CERTIFICATE_SET_X509_SYSTEM_TRUST, 1)], [])
     AC_CHECK_FUNC(gnutls_pkcs12_simple_parse,
                 [AC_DEFINE(HAVE_GNUTLS_PKCS12_SIMPLE_PARSE, 1)], [])
-    AC_CHECK_FUNC(gnutls_session_set_premaster,
-                [AC_DEFINE(HAVE_GNUTLS_SESSION_SET_PREMASTER, 1)], [])
+    if test "$with_openssl" != "" || test "$with_openssl" = "no"; then
+       AC_CHECK_FUNC(gnutls_session_set_premaster,
+                [have_gnutls_dtls=yes], [have_gnutls_dtls=no])
+    else
+       have_gnutls_dtls=no
+    fi
+    if test "$have_gnutls_dtls" = "yes"; then
+       if test "$with_openssl" = "" || test "$with_openssl" = "no"; then
+           # They either said no OpenSSL or didn't specify, and GnuTLS can
+           # do DTLS, so just use GnuTLS.
+            AC_DEFINE(HAVE_GNUTLS_SESSION_SET_PREMASTER, 1)    
+           ssl_library=gnutls
+           with_openssl=no
+       else
+           # They specifically asked for OpenSSL, so use it for DTLS even
+           # though GnuTLS could manage.
+           ssl_library=both
+       fi
+    else
+       if test "$with_openssl" = "no"; then
+           # GnuTLS doesn't have DTLS, but they don't want OpenSSL. So build
+           # without DTLS support at all.
+           ssl_library=gnutls
+       else
+           # GnuTLS doesn't have DTLS so use OpenSSL for it, but GnuTLS for
+           # the TCP connection (and thus in the library).
+           ssl_library=both
+       fi
+    fi
     AC_CHECK_FUNC(gnutls_pkcs11_add_provider,
                 [PKG_CHECK_MODULES(P11KIT, p11-kit-1, [AC_DEFINE(HAVE_P11KIT)
                                          AC_SUBST(P11KIT_PC, p11-kit-1)], [:])], [])
@@ -212,8 +245,7 @@ if test "$with_gnutls" = "yes" || test "$with_gnutls" = "shibboleet"; then
 elif test "$with_gnutls" != "" && test "$with_gnutls" != "no"; then
     AC_MSG_ERROR([Values other than 'yes' or 'no' for --with-gnutls are not supported])
 fi
-
-if test "$with_openssl" = "yes" || test "$with_openssl" = "" ; then
+if test "$with_openssl" = "yes" || test "$with_openssl" = "" || test "$ssl_library" = "both"; then
     PKG_CHECK_MODULES(OPENSSL, openssl, [],
        [oldLIBS="$LIBS"
         LIBS="$LIBS -lssl -lcrypto"
@@ -229,9 +261,15 @@ if test "$with_openssl" = "yes" || test "$with_openssl" = "" ; then
                         AC_SUBST([OPENSSL_LIBS], ["-lssl -lcrypto"])
                         AC_SUBST([OPENSSL_CFLAGS], [])],
                        [AC_MSG_RESULT(no)
-                        AC_ERROR([Could not build against OpenSSL])])
+                        if test "$ssl_library" = "both"; then
+                            ssl_library="gnutls";
+                        else
+                            AC_ERROR([Could not build against OpenSSL]);
+                        fi])
         LIBS="$oldLIBS"])
-    ssl_library=openssl
+    if test "$ssl_library" != "both" && test "$ssl_library" != "gnutls"; then
+        ssl_library=openssl
+    fi
 elif test "$with_openssl" != "no" ; then
     OPENSSL_CFLAGS="-I${with_openssl}/include"
     OPENSSL_LIBS="${with_openssl}/libssl.a ${with_openssl}/libcrypto.a -ldl -lz"
@@ -239,25 +277,41 @@ elif test "$with_openssl" != "no" ; then
     AC_SUBST(OPENSSL_LIBS)
     enable_static=yes
     enable_shared=no
-    ssl_library=openssl
+    AC_DEFINE(DTLS_OPENSSL, 1)
+    if test "$ssl_library" != "both"; then
+        ssl_library=openssl
+    fi
 fi
 
 case "$ssl_library" in
     gnutls)
        AC_DEFINE(OPENCONNECT_GNUTLS, 1)
-       AC_SUBST(SSL_LIBS, [$GNUTLS_LIBS])
-       AC_SUBST(SSL_CFLAGS, [$GNUTLS_CFLAGS])
+       AC_DEFINE(DTLS_GNUTLS, 1)
+       AC_SUBST(SSL_LIBRARY, [gnutls])
+       AC_SUBST(SSL_LIBS, ['$(GNUTLS_LIBS)'])
+       AC_SUBST(SSL_CFLAGS, ['$(GNUTLS_CFLAGS)'])
        ;;
     openssl)
        AC_DEFINE(OPENCONNECT_OPENSSL, 1)
-       AC_SUBST(SSL_LIBS, [$OPENSSL_LIBS])
-       AC_SUBST(SSL_CFLAGS, [$OPENSSL_CFLAGS])
+       AC_DEFINE(DTLS_OPENSSL, 1)
+       AC_SUBST(SSL_LIBRARY, [openssl])
+       AC_SUBST(SSL_LIBS, ['$(OPENSSL_LIBS)'])
+       AC_SUBST(SSL_CFLAGS, ['$(OPENSSL_CFLAGS)'])
+       ;;
+    both)
+       # GnuTLS for TCP, OpenSSL for DTLS
+       AC_DEFINE(OPENCONNECT_GNUTLS, 1)
+       AC_DEFINE(DTLS_OPENSSL, 1)
+       AC_SUBST(SSL_LIBRARY, [gnutls])
+       AC_SUBST(SSL_LIBS, ['$(GNUTLS_LIBS)'])
+       AC_SUBST(SSL_CFLAGS, ['$(GNUTLS_CFLAGS)'])
+       AC_SUBST(DTLS_SSL_LIBS, ['$(OPENSSL_LIBS)'])
+       AC_SUBST(DTLS_SSL_CFLAGS, ['$(OPENSSL_CFLAGS)'])
        ;;
     *)
        AC_MSG_ERROR([Neither OpenSSL nor GnuTLS selected for SSL.])
        ;;
 esac
-AC_SUBST(SSL_LIBRARY, $ssl_library)
 
 # Needs to happen after we default to static/shared libraries based on OpenSSL
 AC_PROG_LIBTOOL
@@ -337,28 +391,28 @@ AC_CHECK_HEADER([if_tun.h],
             [AC_CHECK_HEADER([net/tun/if_tun.h],
                 [AC_DEFINE([IF_TUN_HDR], ["net/tun/if_tun.h"])])])])])
 
-if test "${ssl_library}" = "openssl"; then
+if test "$ssl_library" = "openssl" || test "$ssl_library" = "both"; then
     oldLIBS="$LIBS"
     LIBS="$LIBS $OPENSSL_LIBS"
 
-    AC_MSG_CHECKING([for ENGINE_by_id() in OpenSSL])
-    AC_LINK_IFELSE([AC_LANG_PROGRAM(
-       [#include <openssl/engine.h>],
-       [ENGINE_by_id("foo");])],
-       [AC_MSG_RESULT(yes)
-        AC_DEFINE(HAVE_ENGINE, [1], [OpenSSL has ENGINE support])],
-       [AC_MSG_RESULT(no)
-        AC_MSG_NOTICE([Building without OpenSSL TPM ENGINE support])])
+    if test "$ssl_library" = "openssl"; then
+       AC_MSG_CHECKING([for ENGINE_by_id() in OpenSSL])
+       AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <openssl/engine.h>],
+                                       [ENGINE_by_id("foo");])],
+                      [AC_MSG_RESULT(yes)
+                       AC_DEFINE(HAVE_ENGINE, [1], [OpenSSL has ENGINE support])],
+                      [AC_MSG_RESULT(no)
+                       AC_MSG_NOTICE([Building without OpenSSL TPM ENGINE support])])
+    fi
 
     AC_MSG_CHECKING([for dtls1_stop_timer() in OpenSSL])
-    AC_LINK_IFELSE([AC_LANG_PROGRAM(
-       [#include <openssl/ssl.h>
-        #include <stdlib.h>
-        extern void dtls1_stop_timer(SSL *);],
-       [dtls1_stop_timer(NULL);])],
-       [AC_MSG_RESULT(yes)
-        AC_DEFINE(HAVE_DTLS1_STOP_TIMER, [1], [OpenSSL has dtls1_stop_timer() function])],
-       [AC_MSG_RESULT(no)])
+    AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <openssl/ssl.h>
+                                    #include <stdlib.h>
+                                    extern void dtls1_stop_timer(SSL *);],
+                                   [dtls1_stop_timer(NULL);])],
+                  [AC_MSG_RESULT(yes)
+                   AC_DEFINE(HAVE_DTLS1_STOP_TIMER, [1], [OpenSSL has dtls1_stop_timer() function])],
+                  [AC_MSG_RESULT(no)])
     LIBS="$oldLIBS"
 fi
 
diff --git a/dtls.c b/dtls.c
index 9b62761..6a849ac 100644 (file)
--- a/dtls.c
+++ b/dtls.c
@@ -30,6 +30,9 @@
 #include <netinet/in.h>
 #include <fcntl.h>
 #include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
 
 #include "openconnect-internal.h"
 
@@ -108,7 +111,7 @@ int RAND_bytes(char *buf, int len)
  * their clients use anyway.
  */
 
-#if defined (OPENCONNECT_OPENSSL)
+#if defined (DTLS_OPENSSL)
 #define DTLS_SEND SSL_write
 #define DTLS_RECV SSL_read
 static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
@@ -125,6 +128,7 @@ static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
                if (!vpninfo->dtls_ctx) {
                        vpn_progress(vpninfo, PRG_ERR,
                                     _("Initialise DTLSv1 CTX failed\n"));
+                       openconnect_report_ssl_errors(vpninfo);
                        vpninfo->dtls_attempt_period = 0;
                        return -EINVAL;
                }
@@ -213,7 +217,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        int ret = SSL_do_handshake(vpninfo->new_dtls_ssl);
 
        if (ret == 1) {
-               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection\n"));
+               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using OpenSSL)\n"));
 
                if (vpninfo->dtls_ssl) {
                        /* We are replacing an old connection */
@@ -326,7 +330,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        return -EINVAL;
 }
 
-#elif defined (OPENCONNECT_GNUTLS)
+#elif defined (DTLS_GNUTLS)
 struct {
        const char *name;
        gnutls_cipher_algorithm_t cipher;
@@ -400,7 +404,7 @@ int dtls_try_handshake(struct openconnect_info *vpninfo)
        int err = gnutls_handshake(vpninfo->new_dtls_ssl);
 
        if (!err) {
-               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection\n"));
+               vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using GnuTLS)\n"));
 
                if (vpninfo->dtls_ssl) {
                        /* We are replacing an old connection */
@@ -517,9 +521,9 @@ int connect_dtls_socket(struct openconnect_info *vpninfo)
 static int dtls_restart(struct openconnect_info *vpninfo)
 {
        if (vpninfo->dtls_ssl) {
-#if defined (OPENCONNECT_OPENSSL)
+#if defined (DTLS_OPENSSL)
                SSL_free(vpninfo->dtls_ssl);
-#elif defined (OPENCONNECT_GNUTLS)
+#elif defined (DTLS_GNUTLS)
                gnutls_deinit(vpninfo->dtls_ssl);
 #endif
                close(vpninfo->dtls_fd);
@@ -539,6 +543,15 @@ int setup_dtls(struct openconnect_info *vpninfo)
        struct vpn_option *dtls_opt = vpninfo->dtls_options;
        int dtls_port = 0;
 
+#if defined (OPENCONNECT_GNUTLS) && defined (DTLS_OPENSSL)
+       /* If we're using GnuTLS for authentication but OpenSSL for DTLS,
+          we'll need to initialise OpenSSL now... */
+       SSL_library_init ();
+       ERR_clear_error ();
+       SSL_load_error_strings ();
+       OpenSSL_add_all_algorithms ();
+#endif
+
        while (dtls_opt) {
                vpn_progress(vpninfo, PRG_TRACE,
                             _("DTLS option %s : %s\n"),
@@ -741,7 +754,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
                /* One byte of header */
                this->hdr[7] = AC_PKT_DATA;
 
-#if defined(OPENCONNECT_OPENSSL)
+#if defined(DTLS_OPENSSL)
                ret = SSL_write(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
                if (ret <= 0) {
                        ret = SSL_get_error(vpninfo->dtls_ssl, ret);
@@ -759,7 +772,7 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
                        }
                        return 1;
                }
-#elif defined (OPENCONNECT_GNUTLS)
+#elif defined (DTLS_GNUTLS)
                ret = gnutls_record_send(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
                if (ret <= 0) {
                        if (ret != GNUTLS_E_AGAIN) {
index 8282fdd..91a9731 100644 (file)
 
 #include "openconnect.h"
 
-#if defined (OPENCONNECT_OPENSSL)
+#if defined (OPENCONNECT_OPENSSL) || defined(DTLS_OPENSSL)
 #include <openssl/ssl.h>
-#elif defined (OPENCONNECT_GNUTLS)
+#include <openssl/err.h>
+#endif
+#if defined (OPENCONNECT_GNUTLS)
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
 #endif
@@ -183,12 +185,12 @@ struct openconnect_info {
        int reconnect_interval;
        int dtls_attempt_period;
        time_t new_dtls_started;
-#if defined(OPENCONNECT_OPENSSL)
+#if defined(DTLS_OPENSSL)
        SSL_CTX *dtls_ctx;
        SSL *dtls_ssl;
        SSL *new_dtls_ssl;
        SSL_SESSION *dtls_session;
-#elif defined(OPENCONNECT_GNUTLS)
+#elif defined(DTLS_GNUTLS)
        /* Call these *_ssl rather than *_sess because they're just
           pointers, and generic code (in mainloop.c for example)
           wants to check if they're NULL or not. No point in being
@@ -256,8 +258,8 @@ struct openconnect_info {
        openconnect_progress_vfn progress;
 };
 
-#if (defined (OPENCONNECT_OPENSSL) && defined (SSL_OP_CISCO_ANYCONNECT)) || \
-    (defined(OPENCONNECT_GNUTLS) && defined (HAVE_GNUTLS_SESSION_SET_PREMASTER))
+#if (defined (DTLS_OPENSSL) && defined (SSL_OP_CISCO_ANYCONNECT)) || \
+    (defined (DTLS_GNUTLS) && defined (HAVE_GNUTLS_SESSION_SET_PREMASTER))
 #define HAVE_DTLS 1
 #endif
 
@@ -272,11 +274,13 @@ struct openconnect_info {
 #define AC_PKT_TERM_SERVER     9       /* Server kick */
 
 /* Ick */
+#ifdef DTLS_OPENSSL
 #if OPENSSL_VERSION_NUMBER >= 0x00909000L
 #define method_const const
 #else
 #define method_const
 #endif
+#endif
 
 #define vpn_progress(vpninfo, ...) (vpninfo)->progress ((vpninfo)->cbdata, __VA_ARGS__)
 
@@ -322,6 +326,9 @@ int request_passphrase(struct openconnect_info *vpninfo,
                       char **response, const char *fmt, ...);
 int  __attribute__ ((format (printf, 2, 3)))
     openconnect_SSL_printf(struct openconnect_info *vpninfo, const char *fmt, ...);
+#if defined(OPENCONNECT_OPENSSL) || defined (DTLS_OPENSSL)
+void openconnect_report_ssl_errors(struct openconnect_info *vpninfo);
+#endif
 
 /* ${SSL_LIBRARY}.c */
 int openconnect_SSL_gets(struct openconnect_info *vpninfo, char *buf, size_t len);
@@ -331,8 +338,6 @@ int openconnect_open_https(struct openconnect_info *vpninfo);
 void openconnect_close_https(struct openconnect_info *vpninfo, int final);
 int get_cert_md5_fingerprint(struct openconnect_info *vpninfo, OPENCONNECT_X509 *cert,
                             char *buf);
-/* This one is actually OpenSSL-specific */
-void openconnect_report_ssl_errors(struct openconnect_info *vpninfo);
 int openconnect_sha1(unsigned char *result, void *data, int len);
 int openconnect_random(void *bytes, int len);
 int openconnect_local_cert_md5(struct openconnect_info *vpninfo,
index d9ea41e..adbb977 100644 (file)
--- a/openssl.c
+++ b/openssl.c
@@ -163,19 +163,6 @@ int openconnect_SSL_read(struct openconnect_info *vpninfo, char *buf, size_t len
        return done;
 }
 
-static int print_err(const char *str, size_t len, void *ptr)
-{
-       struct openconnect_info *vpninfo = ptr;
-
-       vpn_progress(vpninfo, PRG_ERR, "%s", str);
-       return 0;
-}
-
-void openconnect_report_ssl_errors(struct openconnect_info *vpninfo)
-{
-       ERR_print_errors_cb(print_err, vpninfo);
-}
-
 int openconnect_SSL_gets(struct openconnect_info *vpninfo, char *buf, size_t len)
 {
        int i = 0;
diff --git a/ssl.c b/ssl.c
index e059397..895bfe5 100644 (file)
--- a/ssl.c
+++ b/ssl.c
@@ -353,3 +353,21 @@ int openconnect_passphrase_from_fsid(struct openconnect_info *vpninfo)
        return 0;
 }
 #endif
+
+#if defined(OPENCONNECT_OPENSSL) || defined (DTLS_OPENSSL)
+/* We put this here rather than in openssl.c because it might be needed
+   for OpenSSL DTLS support even when GnuTLS is being used for HTTPS */
+#include <openssl/err.h>
+static int print_err(const char *str, size_t len, void *ptr)
+{
+       struct openconnect_info *vpninfo = ptr;
+
+       vpn_progress(vpninfo, PRG_ERR, "%s", str);
+       return 0;
+}
+
+void openconnect_report_ssl_errors(struct openconnect_info *vpninfo)
+{
+       ERR_print_errors_cb(print_err, vpninfo);
+}
+#endif