qemu already makes use of gnutls, so it is only natural to extend its
use to the IRC char driver. Furthermore, though IRC is ubiquitous, there
may be servers which allow only SSL connections for some sense of
additional security, be it real or not. Adding that support to qemu
therefore means greater compatibility which should be the goal of any
software implementing an open standard such as IRC.

The Heartbleed crisis influences this patch in two ways: First, it does
not use OpenSSL. As GnuTLS is not nearly used as commonly, this will
give us an advantage regarding attackers which prefer to attack the most
widespread platform. Second, certificates can be stolen any time and
revoking them does not work. Therefore, authentication in SSL is broken
by design and we do not need to care for it. This guideline is generally
followed by IRC servers as well, for instance OFTC, which hosts the
#qemu IRC channel, does offer a self-signed certificate. Thus, this
policy seems widely accepted and to be current industry practice.

Signed-off-by: Max Reitz <mre...@redhat.com>
---
 configure        |  43 ++++++++++++++++------
 qapi-schema.json |   3 +-
 qemu-char.c      | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+), 12 deletions(-)

diff --git a/configure b/configure
index 09c9225..63ea221 100755
--- a/configure
+++ b/configure
@@ -246,6 +246,7 @@ vnc_sasl=""
 vnc_jpeg=""
 vnc_png=""
 vnc_ws=""
+gnutls=""
 xen=""
 xen_ctrl_version=""
 xen_pci_passthrough=""
@@ -889,6 +890,10 @@ for opt do
   ;;
   --enable-vnc-ws) vnc_ws="yes"
   ;;
+  --disable-gnutls) gnutls="no"
+  ;;
+  --enable-gnutls) gnutls="yes"
+  ;;
   --disable-slirp) slirp="no"
   ;;
   --disable-uuid) uuid="no"
@@ -2283,32 +2288,43 @@ EOF
 fi
 
 ##########################################
-# VNC TLS/WS detection
-if test "$vnc" = "yes" -a \( "$vnc_tls" != "no" -o "$vnc_ws" != "no" \) ; then
+# GnuTLS detection
+if test "$gnutls" != "no" ; then
   cat > $TMPC <<EOF
 #include <gnutls/gnutls.h>
 int main(void) { gnutls_session_t s; gnutls_init(&s, GNUTLS_SERVER); return 0; 
}
 EOF
-  vnc_tls_cflags=`$pkg_config --cflags gnutls 2> /dev/null`
-  vnc_tls_libs=`$pkg_config --libs gnutls 2> /dev/null`
-  if compile_prog "$vnc_tls_cflags" "$vnc_tls_libs" ; then
+  gnutls_cflags=`$pkg_config --cflags gnutls 2> /dev/null`
+  gnutls_libs=`$pkg_config --libs gnutls 2> /dev/null`
+  if compile_prog "$gnutls_cflags" "$gnutls_libs" ; then
+    gnutls=yes
+    libs_softmmu="$gnutls_libs $libs_softmmu"
+    QEMU_CFLAGS="$QEMU_CFLAGS $gnutls_cflags"
+  else
+    if test "$gnutls" = "yes" ; then
+      feature_not_found "gnutls" "Install gnutls devel"
+    fi
+    gnutls=no
+  fi
+fi
+
+##########################################
+# VNC TLS/WS detection
+if test "$vnc" = "yes" -a \( "$vnc_tls" != "no" -o "$vnc_ws" != "no" \) ; then
+  if test "$gnutls" = "yes" ; then
     if test "$vnc_tls" != "no" ; then
       vnc_tls=yes
     fi
     if test "$vnc_ws" != "no" ; then
       vnc_ws=yes
     fi
-    libs_softmmu="$vnc_tls_libs $libs_softmmu"
-    QEMU_CFLAGS="$QEMU_CFLAGS $vnc_tls_cflags"
   else
     if test "$vnc_tls" = "yes" ; then
-      feature_not_found "vnc-tls" "Install gnutls devel"
+      feature_not_found "vnc-tls" "Enable gnutls"
     fi
     if test "$vnc_ws" = "yes" ; then
-      feature_not_found "vnc-ws" "Install gnutls devel"
+      feature_not_found "vnc-ws" "Enable gnutls"
     fi
-    vnc_tls=no
-    vnc_ws=no
   fi
 fi
 
@@ -4440,6 +4456,7 @@ echo "lzo support       $lzo"
 echo "snappy support    $snappy"
 echo "bzip2 support     $bzip2"
 echo "NUMA host support $numa"
+echo "GnuTLS support    $gnutls"
 
 if test "$sdl_too_old" = "yes"; then
 echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -4921,6 +4938,10 @@ if test "$tpm" = "yes"; then
   fi
 fi
 
+if test "$gnutls" = "yes" ; then
+  echo "CONFIG_GNUTLS=y" >> $config_host_mak
+fi
+
 echo "TRACE_BACKENDS=$trace_backends" >> $config_host_mak
 if have_backend "nop"; then
   echo "CONFIG_TRACE_NOP=y" >> $config_host_mak
diff --git a/qapi-schema.json b/qapi-schema.json
index f427e04..e4f93fd 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2888,7 +2888,8 @@
 ##
 { 'type': 'ChardevIrc', 'data': { 'addr'        : 'SocketAddress',
                                   'nick'        : 'str',
-                                  'channel'     : 'str' } }
+                                  'channel'     : 'str',
+                                  'ssl'         : 'bool' } }
 
 ##
 # @ChardevBackend:
diff --git a/qemu-char.c b/qemu-char.c
index 4507e5f..1030d5e 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -81,6 +81,9 @@
 #endif
 #endif
 #endif
+#ifdef CONFIG_GNUTLS
+#include <gnutls/gnutls.h>
+#endif
 
 #include "qemu/sockets.h"
 #include "ui/qemu-spice.h"
@@ -3306,6 +3309,11 @@ typedef struct {
 
     GIOChannel *chan;
 
+#ifdef CONFIG_GNUTLS
+    gnutls_session_t tls_session;
+    gnutls_certificate_credentials_t tls_cred;
+#endif
+
     uint8_t *sendbuf;
     int sendbuf_idx, sendbuf_prefixlen;
     bool send_line_skip;
@@ -3315,6 +3323,38 @@ typedef struct {
     bool recv_line_skip;
 } IrcCharDriverState;
 
+#ifdef CONFIG_GNUTLS
+static ssize_t irc_send(IrcCharDriverState *irc, const void *buf, size_t len)
+{
+    if (irc->tls_session) {
+        return gnutls_record_send(irc->tls_session, buf, len);
+    } else {
+        return send_all(irc->fd, buf, len);
+    }
+}
+
+static ssize_t irc_recv(IrcCharDriverState *irc, void *buf, size_t len)
+{
+    if (irc->tls_session) {
+        ssize_t ret = gnutls_record_recv(irc->tls_session, buf, len);
+        if (ret >= 0) {
+            return ret;
+        } else if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) {
+            return irc_recv(irc, buf, len);
+        } else if (ret == GNUTLS_E_REHANDSHAKE) {
+            ret = gnutls_handshake(irc->tls_session);
+            if (ret != GNUTLS_E_SUCCESS) {
+                return -1;
+            }
+            return irc_recv(irc, buf, len);
+        } else {
+            return -1;
+        }
+    } else {
+        return recv_all(irc->fd, buf, len, true);
+    }
+}
+#else
 static ssize_t irc_send(IrcCharDriverState *irc, const void *buf, size_t len)
 {
     return send_all(irc->fd, buf, len);
@@ -3324,6 +3364,7 @@ static ssize_t irc_recv(IrcCharDriverState *irc, void 
*buf, size_t len)
 {
     return recv_all(irc->fd, buf, len, true);
 }
+#endif
 
 static int irc_read_line(IrcCharDriverState *irc, char **buf)
 {
@@ -3569,6 +3610,13 @@ static void irc_chr_update_read_handler(CharDriverState 
*s)
 static void irc_destroy(IrcCharDriverState *irc)
 {
     irc_send(irc, "QUIT\r\n", strlen("QUIT\r\n"));
+#ifdef CONFIG_GNUTLS
+    if (irc->tls_session) {
+        gnutls_bye(irc->tls_session, GNUTLS_SHUT_RDWR);
+        gnutls_certificate_free_credentials(irc->tls_cred);
+        gnutls_deinit(irc->tls_session);
+    }
+#endif
     g_io_channel_unref(irc->chan);
     close(irc->fd);
 
@@ -3938,6 +3986,7 @@ static void qemu_chr_parse_irc(QemuOpts *opts, 
ChardevBackend *backend,
                                Error **errp)
 {
     const char *host, *port, *nick, *channel, *sockfd;
+    bool ssl;
     SocketAddress *addr;
 
     host    = qemu_opt_get(opts, "host");
@@ -3945,6 +3994,11 @@ static void qemu_chr_parse_irc(QemuOpts *opts, 
ChardevBackend *backend,
     sockfd  = qemu_opt_get(opts, "sockfd");
     nick    = qemu_opt_get(opts, "nick");
     channel = qemu_opt_get(opts, "channel");
+#ifdef CONFIG_GNUTLS
+    ssl     = qemu_opt_get_bool(opts, "ssl", false);
+#else
+    ssl     = false;
+#endif
 
     if ((!host && !sockfd) || !nick || !channel) {
         error_setg(errp, "chardev: irc: Missing options");
@@ -3980,6 +4034,7 @@ static void qemu_chr_parse_irc(QemuOpts *opts, 
ChardevBackend *backend,
 
     backend->irc->nick = g_strdup(nick);
     backend->irc->channel = g_strdup(channel);
+    backend->irc->ssl = ssl;
 }
 
 typedef struct CharDriver {
@@ -4349,6 +4404,11 @@ QemuOptsList qemu_chardev_opts = {
         },{
             .name = "sockfd",
             .type = QEMU_OPT_STRING,
+#ifdef CONFIG_GNUTLS
+        },{
+            .name = "ssl",
+            .type = QEMU_OPT_BOOL,
+#endif
         },
         { /* end of list */ }
     },
@@ -4618,6 +4678,52 @@ static CharDriverState *qemu_chr_open_irc(ChardevIrc 
*irc, Error **errp)
     irc_cds->recvbuf = g_malloc(1024);
     irc_cds->recvbuf_idx = 0;
 
+#ifdef CONFIG_GNUTLS
+    if (irc->ssl) {
+        ret = gnutls_init(&irc_cds->tls_session, GNUTLS_CLIENT);
+        if (ret != GNUTLS_E_SUCCESS) {
+            error_setg(errp, "Failed to initialize GnuTLS session: %s",
+                       gnutls_strerror(ret));
+            goto fail;
+        }
+
+        gnutls_transport_set_ptr(irc_cds->tls_session,
+                                 (gnutls_transport_ptr_t)(uintptr_t)fd);
+        gnutls_transport_set_push_function(irc_cds->tls_session, &push_all);
+        gnutls_transport_set_pull_function(irc_cds->tls_session, &pull_all);
+
+        ret = gnutls_priority_set_direct(irc_cds->tls_session, "NORMAL", NULL);
+        if (ret != GNUTLS_E_SUCCESS) {
+            error_setg(errp, "Setting TLS priorities failed: %s",
+                       gnutls_strerror(ret));
+            goto fail;
+        }
+
+        ret = gnutls_certificate_allocate_credentials(&irc_cds->tls_cred);
+        if (ret != GNUTLS_E_SUCCESS) {
+            error_setg(errp, "TLS credential allocation failed: %s",
+                       gnutls_strerror(ret));
+            goto fail;
+        }
+
+        gnutls_certificate_set_verify_function(irc_cds->tls_cred,
+                                               gnutls_cert_allow_all);
+        ret = gnutls_credentials_set(irc_cds->tls_session,
+                                     GNUTLS_CRD_CERTIFICATE, 
irc_cds->tls_cred);
+        if (ret != GNUTLS_E_SUCCESS) {
+            error_setg(errp, "TLS credential association failed: %s",
+                       gnutls_strerror(ret));
+            goto fail;
+        }
+
+        ret = gnutls_handshake(irc_cds->tls_session);
+        if (ret != GNUTLS_E_SUCCESS) {
+            error_setg(errp, "TLS handshake failed: %s", gnutls_strerror(ret));
+            goto fail;
+        }
+    }
+#endif
+
     sendstr = g_strdup_printf("USER qemu qemu qemu qemu\r\nNICK %s\r\n",
                               irc->nick);
     ret = irc_send(irc_cds, sendstr, strlen(sendstr));
-- 
2.3.4


Reply via email to