[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-devel] [PATCH RFC 5/6] qemu-xen-trad: sasl: introduce SASL authentication and encryption layer



This change adds calls to the SASL API to negotiate SASL auth and
includes SASL encode/decode into read and write flows if the SASL
mechanism is providing SSF.

The code is taken from upstream with minor adjustments for
compatibility with qemu-xen-traditional.

Signed-off-by: Simon Waterman <watermansrdev@xxxxxxxxx>
---
 vnc.c | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 292 insertions(+), 37 deletions(-)

diff --git a/vnc.c b/vnc.c
index 728efec..ff460b8 100644
--- a/vnc.c
+++ b/vnc.c
@@ -80,6 +80,58 @@ static DisplayChangeListener *dcl;
   (((x) + (1ULL << (vs)->dirty_pixel_shift) - 1) >> (vs)->dirty_pixel_shift)
 #define DP2X(vs, x) ((x) << (vs)->dirty_pixel_shift)
 
+#ifndef CONFIG_STUBDOM
+static char *addr_to_string(const char *format,
+                            struct sockaddr_storage *sa,
+                            socklen_t salen) {
+    char *addr;
+    char host[NI_MAXHOST];
+    char serv[NI_MAXSERV];
+    int err;
+    size_t addrlen;
+
+    if ((err = getnameinfo((struct sockaddr *)sa, salen,
+                           host, sizeof(host),
+                           serv, sizeof(serv),
+                           NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
+        VNC_DEBUG("Cannot resolve address %d: %s\n",
+                  err, gai_strerror(err));
+        return NULL;
+    }
+
+    /* Enough for the existing format + the 2 vars we're
+     * substituting in. */
+    addrlen = strlen(format) + strlen(host) + strlen(serv);
+    addr = malloc(addrlen + 1);
+    snprintf(addr, addrlen, format, host, serv);
+    addr[addrlen] = '\0';
+
+    return addr;
+}
+
+char *vnc_socket_local_addr(const char *format, int fd) {
+    struct sockaddr_storage sa;
+    socklen_t salen;
+
+    salen = sizeof(sa);
+    if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0)
+        return NULL;
+
+    return addr_to_string(format, &sa, salen);
+}
+
+char *vnc_socket_remote_addr(const char *format, int fd) {
+    struct sockaddr_storage sa;
+    socklen_t salen;
+
+    salen = sizeof(sa);
+    if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0)
+        return NULL;
+
+    return addr_to_string(format, &sa, salen);
+}
+#endif /* !CONFIG_STUBDOM */
+
 void do_info_vnc(void)
 {
     if (vnc_state == NULL)
@@ -770,6 +822,9 @@ int vnc_client_io_error(VncState *vs, int ret, int 
last_errno)
        }
        vs->wiremode = VNC_WIREMODE_CLEAR;
 #endif /* CONFIG_VNC_TLS */
+#ifdef CONFIG_VNC_SASL
+        vnc_sasl_client_cleanup(vs);
+#endif /* CONFIG_VNC_SASL */
        return 0;
     }
     return ret;
@@ -780,65 +835,203 @@ void vnc_client_error(VncState *vs)
     vnc_client_io_error(vs, -1, EINVAL);
 }
 
-static void vnc_client_write(void *opaque)
+#ifdef CONFIG_VNC_TLS
+static long vnc_client_write_tls(gnutls_session_t *session,
+                                 const uint8_t *data,
+                                 size_t datalen)
+{
+    long ret = gnutls_write(*session, data, datalen);
+    if (ret < 0) { 
+        if (ret == GNUTLS_E_AGAIN) {
+            errno = EAGAIN;
+        } else {
+            errno = EIO;
+        }
+        ret = -1;
+    }
+    return ret;
+}
+#endif /* CONFIG_VNC_TLS */
+
+/*
+ * Called to write a chunk of data to the client socket. The data may
+ * be the raw data, or may have already been encoded by SASL.
+ * The data will be written either straight onto the socket, or
+ * written via the GNUTLS wrappers, if TLS/SSL encryption is enabled
+ *
+ * NB, it is theoretically possible to have 2 layers of encryption,
+ * both SASL, and this TLS layer. It is highly unlikely in practice
+ * though, since SASL encryption will typically be a no-op if TLS
+ * is active
+ *
+ * Returns the number of bytes written, which may be less than
+ * the requested 'datalen' if the socket would block. Returns
+ * -1 on error, and disconnects the client socket.
+ */
+long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
 {
     long ret;
-    VncState *vs = opaque;
-
 #ifdef CONFIG_VNC_TLS
     if (vs->tls_session) {
-       ret = gnutls_write(vs->tls_session, vs->output.buffer, 
vs->output.offset);
-       if (ret < 0) {
-           if (ret == GNUTLS_E_AGAIN)
-               errno = EAGAIN;
-           else
-               errno = EIO;
-           ret = -1;
-       }
-    } else
+        ret = vnc_client_write_tls(&vs->tls_session, data, datalen);
+    } else {
+#endif /* CONFIG_VNC_TLS */
+        ret = send(vs->csock, data, datalen, 0);
+#ifdef CONFIG_VNC_TLS
+    }
 #endif /* CONFIG_VNC_TLS */
-       ret = send(vs->csock, vs->output.buffer, vs->output.offset, 0);
-    ret = vnc_client_io_error(vs, ret, socket_error());
+    VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret);
+    return vnc_client_io_error(vs, ret, socket_error());
+}
+
+/*
+ * Called to write buffered data to the client socket, when not
+ * using any SASL SSF encryption layers. Will write as much data
+ * as possible without blocking. If all buffered data is written,
+ * will switch the FD poll() handler back to read monitoring.
+ *
+ * Returns the number of bytes written, which may be less than
+ * the buffered output data if the socket would block. Returns
+ * -1 on error, and disconnects the client socket.
+ */
+static long vnc_client_write_plain(VncState *vs)
+{
+    long ret;
+
+#ifdef CONFIG_VNC_SASL
+    VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF 
%d\n",
+              vs->output.buffer, vs->output.capacity, vs->output.offset,
+              vs->sasl.waitWriteSSF);
+
+    if (vs->sasl.conn &&
+        vs->sasl.runSSF &&
+        vs->sasl.waitWriteSSF) {
+        ret = vnc_client_write_buf(vs, vs->output.buffer, 
vs->sasl.waitWriteSSF);
+        if (ret)
+            vs->sasl.waitWriteSSF -= ret;
+    } else
+#endif /* CONFIG_VNC_SASL */
+        ret = vnc_client_write_buf(vs, vs->output.buffer, vs->output.offset);
     if (!ret)
-       return;
+        return 0;
 
     buffer_advance(&vs->output, ret);
 
-    if (vs->output.offset == 0)
-       qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
+    if (vs->output.offset == 0) {
+        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
+    }
+
+    return ret;
 }
 
-static void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
+/*
+ * First function called whenever there is data to be written to
+ * the client socket. Will delegate actual work according to whether
+ * SASL SSF layers are enabled (thus requiring encryption calls)
+ */
+static void vnc_client_write(void *opaque)
+{
+    long ret;
+    VncState *vs = opaque;
+
+#ifdef CONFIG_VNC_SASL
+    if (vs->sasl.conn &&
+        vs->sasl.runSSF &&
+        !vs->sasl.waitWriteSSF)
+        ret = vnc_client_write_sasl(vs);
+    else
+#endif /* CONFIG_VNC_SASL */
+        ret = vnc_client_write_plain(vs);
+}
+
+void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
 {
     vs->read_handler = func;
     vs->read_handler_expect = expecting;
 }
 
-void vnc_client_read(void *opaque)
+#ifdef CONFIG_VNC_TLS
+static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
+                                size_t datalen)
 {
-    VncState *vs = opaque;
-    long ret;
-
-    buffer_reserve(&vs->input, 4096);
+    long ret = gnutls_read(*session, data, datalen);
+    if (ret < 0) { 
+        if (ret == GNUTLS_E_AGAIN) {
+            errno = EAGAIN;
+        } else {
+            errno = EIO;
+        }
+        ret = -1;
+    }
+    return ret;
+}
+#endif /* CONFIG_VNC_TLS */
 
+/*
+ * Called to read a chunk of data from the client socket. The data may
+ * be the raw data, or may need to be further decoded by SASL.
+ * The data will be read either straight from to the socket, or
+ * read via the GNUTLS wrappers, if TLS/SSL encryption is enabled
+ *
+ * NB, it is theoretically possible to have 2 layers of encryption,
+ * both SASL, and this TLS layer. It is highly unlikely in practice
+ * though, since SASL encryption will typically be a no-op if TLS
+ * is active
+ *
+ * Returns the number of bytes read, which may be less than
+ * the requested 'datalen' if the socket would block. Returns
+ * -1 on error, and disconnects the client socket.
+ */
+long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
+{
+    long ret;
 #ifdef CONFIG_VNC_TLS
     if (vs->tls_session) {
-       ret = gnutls_read(vs->tls_session, buffer_end(&vs->input), 4096);
-       if (ret < 0) {
-           if (ret == GNUTLS_E_AGAIN)
-               errno = EAGAIN;
-           else
-               errno = EIO;
-           ret = -1;
-       }
-    } else
+        ret = vnc_client_read_tls(&vs->tls_session, data, datalen);
+    } else {
 #endif /* CONFIG_VNC_TLS */
-       ret = recv(vs->csock, buffer_end(&vs->input), 4096, 0);
-    ret = vnc_client_io_error(vs, ret, socket_error());
-    if (!ret)
-       return;
+        ret = recv(vs->csock, data, datalen, 0);
+#ifdef CONFIG_VNC_TLS
+    }
+#endif /* CONFIG_VNC_TLS */
+    VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret);
+    return vnc_client_io_error(vs, ret, socket_error());
+}
 
+/*
+ * Called to read data from the client socket to the input buffer,
+ * when not using any SASL SSF encryption layers. Will read as much
+ * data as possible without blocking.
+ *
+ * Returns the number of bytes read. Returns -1 on error, and
+ * disconnects the client socket.
+ */
+static long vnc_client_read_plain(VncState *vs)
+{
+    int ret;
+    VNC_DEBUG("Read plain %p size %zd offset %zd\n",
+              vs->input.buffer, vs->input.capacity, vs->input.offset);
+    buffer_reserve(&vs->input, 4096);
+    ret = vnc_client_read_buf(vs, buffer_end(&vs->input), 4096);
+    if (!ret)
+        return 0;
     vs->input.offset += ret;
+    return ret;
+}
+
+void vnc_client_read(void *opaque)
+{
+    VncState *vs = opaque;
+    long ret;
+
+#ifdef CONFIG_VNC_SASL
+    if (vs->sasl.conn && vs->sasl.runSSF)
+        ret = vnc_client_read_sasl(vs);
+    else
+#endif /* CONFIG_VNC_SASL */
+        ret = vnc_client_read_plain(vs);
+    if (!ret)
+        return;
 
     while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) {
        size_t len = vs->read_handler_expect;
@@ -1897,6 +2090,14 @@ static int start_auth_vencrypt_subauth(VncState *vs)
        VNC_DEBUG("Start TLS auth VNC\n");
        return start_auth_vnc(vs);
 
+#ifdef CONFIG_VNC_SASL
+    case VNC_AUTH_VENCRYPT_X509SASL:
+    case VNC_AUTH_VENCRYPT_TLSSASL:
+       VNC_DEBUG("Start TLS auth SASL\n");
+       start_auth_sasl(vs);
+    break;
+#endif /* CONFIG_VNC_SASL */
+
     default: /* Should not be possible, but just in case */
        VNC_DEBUG("Reject auth %d\n", vs->auth);
        vnc_write_u8(vs, 1);
@@ -1957,7 +2158,9 @@ static void vnc_handshake_io(void *opaque) {
 #define NEED_X509_AUTH(vs)                           \
     ((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE ||   \
      (vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC ||    \
-     (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN)
+     (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN || \
+     (vs)->subauth == VNC_AUTH_VENCRYPT_X509SASL ||   \
+     (vs)->subauth == VNC_AUTH_VENCRYPT_TLSSASL)
 
 #if defined(GNUTLS_VERSION_NUMBER) && \
     GNUTLS_VERSION_NUMBER >= 0x020200 /* 2.2.0 */
@@ -2172,6 +2375,13 @@ static int protocol_client_auth(VncState *vs, uint8_t 
*data, size_t len)
            return start_auth_vencrypt(vs);
 #endif /* CONFIG_VNC_TLS */
 
+#ifdef CONFIG_VNC_SASL
+       case VNC_AUTH_SASL:
+           VNC_DEBUG("Accept SASL auth\n");
+           start_auth_sasl(vs);
+           break;
+#endif /* CONFIG_VNC_SASL */
+
        default: /* Should not be possible, but just in case */
            VNC_DEBUG("Reject auth %d\n", vs->auth);
            vnc_write_u8(vs, 1);
@@ -2435,6 +2645,10 @@ int vnc_display_open(DisplayState *ds, const char 
*display, int find_unused)
 #ifdef CONFIG_VNC_TLS
     int tls = 0, x509 = 0;
 #endif
+    bool sasl = false;
+#ifdef CONFIG_VNC_SASL
+    int saslErr;
+#endif
 
     if (display == NULL)
        display = "localhost:0";
@@ -2453,6 +2667,15 @@ int vnc_display_open(DisplayState *ds, const char 
*display, int find_unused)
            password = 1; /* Require password auth */
         } else if (strncmp(options, "switchbpp", 9) == 0) {
             vs->switchbpp = 1;
+       } else if (strncmp(options, "sasl", 4) == 0) {
+#ifdef CONFIG_VNC_SASL
+           sasl = 1; /* Require SASL auth */
+#else
+           fprintf(stderr, "VNC SASL auth requires cyrus-sasl support\n");
+           qemu_free(vs->display);
+           vs->display = NULL;
+           return -1;
+#endif /* CONFIG_VNC_SASL */
 #ifdef CONFIG_VNC_TLS
        } else if (strncmp(options, "tls", 3) == 0) {
            tls = 1; /* Require TLS */
@@ -2509,6 +2732,27 @@ int vnc_display_open(DisplayState *ds, const char 
*display, int find_unused)
            vs->subauth = VNC_AUTH_INVALID;
        }
 #endif
+#ifdef CONFIG_VNC_SASL
+    } else if (sasl) {
+#ifdef CONFIG_VNC_TLS
+        if (tls) {
+            vs->auth = VNC_AUTH_VENCRYPT;
+            if (x509) {
+               VNC_DEBUG("Initializing VNC server with x509 SASL auth\n");
+                vs->subauth = VNC_AUTH_VENCRYPT_X509SASL;
+            } else {
+               VNC_DEBUG("Initializing VNC server with TLS SASL auth\n");
+                vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL;
+            }
+        } else {
+#endif /* CONFIG_VNC_TLS */
+           VNC_DEBUG("Initializing VNC server with SASL auth\n");
+            vs->auth = VNC_AUTH_SASL;
+#ifdef CONFIG_VNC_TLS
+            vs->subauth = VNC_AUTH_INVALID;
+        }
+#endif /* CONFIG_VNC_TLS */
+#endif /* CONFIG_VNC_SASL */
     } else {
 #ifdef CONFIG_VNC_TLS
        if (tls) {
@@ -2529,6 +2773,17 @@ int vnc_display_open(DisplayState *ds, const char 
*display, int find_unused)
        }
 #endif
     }
+
+#ifdef CONFIG_VNC_SASL
+    if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) {
+        fprintf(stderr, "Failed to initialize SASL auth %s",
+                sasl_errstring(saslErr, NULL, NULL));
+        free(vs->display);
+        vs->display = NULL;
+        return -1;
+    }
+#endif
+
 #ifndef NO_UNIX_SOCKETS
     if (strstart(display, "unix:", &p)) {
        addr = (struct sockaddr *)&uaddr;
-- 
2.7.4


_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
https://lists.xen.org/xen-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.