[Spice-devel] [PATCH 13/13] server: add SASL support

Marc-André Lureau marcandre.lureau at redhat.com
Tue Feb 22 08:09:07 PST 2011


We introduce 2 public functions to integrate with the library user.

spice_server_set_sasl() - turn on SASL
spice_server_set_sasl_appname() - specify the name of the app (It is
used for where to find the default configuration file)

The patch for QEMU is on its way.
---
 server/reds.c  |  702 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 server/reds.h  |   39 +++
 server/spice.h |    2 +
 3 files changed, 737 insertions(+), 6 deletions(-)

diff --git a/server/reds.c b/server/reds.c
index 0a26e79..1348ff7 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -16,6 +16,8 @@
    License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "config.h"
+
 #include <stdint.h>
 #include <stdio.h>
 #include <unistd.h>
@@ -38,6 +40,9 @@
 #include <openssl/rsa.h>
 #include <openssl/ssl.h>
 #include <openssl/err.h>
+#if HAVE_SASL
+#include <sasl/sasl.h>
+#endif
 
 #include "spice.h"
 #include "spice-experimental.h"
@@ -53,7 +58,6 @@
 #include <spice/stats.h>
 #include "stat.h"
 #include "ring.h"
-#include "config.h"
 #include "demarshallers.h"
 #include "marshaller.h"
 #include "generated_marshallers.h"
@@ -83,6 +87,10 @@ static int spice_secure_port = -1;
 static char spice_addr[256];
 static int spice_family = PF_UNSPEC;
 static char *default_renderer = "sw";
+static int sasl_enabled = 0; // sasl disabled by default
+#if HAVE_SASL
+static char *sasl_appname = NULL; // default to "spice" if NULL
+#endif
 
 static int ticketing_enabled = 1; //Ticketing is enabled by default
 static pthread_mutex_t *lock_cs;
@@ -1366,7 +1374,10 @@ static void reds_channel_set_common_caps(Channel *channel, int cap, int active)
 
 void reds_channel_init_auth_caps(Channel *channel)
 {
-    reds_channel_set_common_caps(channel, SPICE_COMMON_CAP_AUTH_SPICE, TRUE);
+    if (sasl_enabled)
+        reds_channel_set_common_caps(channel, SPICE_COMMON_CAP_AUTH_SASL, TRUE);
+    else
+        reds_channel_set_common_caps(channel, SPICE_COMMON_CAP_AUTH_SPICE, TRUE);
     reds_channel_set_common_caps(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION, TRUE);
 }
 
@@ -1487,7 +1498,7 @@ static void reds_handle_main_link(RedLinkInfo *link)
     link_mess = link->link_mess;
     reds_disconnect();
 
-    if (!link_mess->connection_id) {
+    if (link_mess->connection_id == 0) {
         reds_send_link_result(link, SPICE_LINK_ERR_OK);
         while((connection_id = rand()) == 0);
         reds->agent_state.num_tokens = 0;
@@ -1671,6 +1682,100 @@ static inline void async_read_clear_handlers(AsyncRead *obj)
     obj->stream->watch = NULL;
 }
 
+#if HAVE_SASL
+static int sync_write_u8(RedsStream *s, uint8_t n)
+{
+    return sync_write(s, &n, sizeof(uint8_t));
+}
+
+static int sync_write_u32(RedsStream *s, uint32_t n)
+{
+    return sync_write(s, &n, sizeof(uint32_t));
+}
+
+ssize_t reds_stream_sasl_write(RedsStream *s, const void *buf, size_t nbyte)
+{
+    ssize_t ret;
+
+    if (!s->sasl.encoded) {
+        int err;
+        err = sasl_encode(s->sasl.conn, (char *)buf, nbyte,
+                          (const char **)&s->sasl.encoded,
+                          &s->sasl.encodedLength);
+        if (err != SASL_OK) {
+            red_printf("sasl_encode error: %d", err);
+            return -1;
+        }
+
+        if (s->sasl.encodedLength == 0)
+            return 0;
+        if (!s->sasl.encoded) {
+            red_printf("sasl_encode didn't return a buffer!");
+            return 0;
+        }
+
+        s->sasl.encodedOffset = 0;
+    }
+
+    ret = s->write(s, s->sasl.encoded + s->sasl.encodedOffset,
+                   s->sasl.encodedLength - s->sasl.encodedOffset);
+
+    if (ret <= 0)
+        return ret;
+
+    s->sasl.encodedOffset += ret;
+    if (s->sasl.encodedOffset == s->sasl.encodedLength) {
+        s->sasl.encoded = NULL;
+        s->sasl.encodedOffset = s->sasl.encodedLength = 0;
+        return nbyte;
+    }
+
+    /* we didn't flush the encoded buffer */
+    errno = EAGAIN;
+    return -1;
+}
+
+static ssize_t reds_stream_sasl_read(RedsStream *s, void *buf, size_t nbyte)
+{
+    uint8_t encoded[4096];
+    const char *decoded;
+    unsigned int decodedlen;
+    int err;
+    int n;
+
+    n = spice_buffer_copy(&s->sasl.inbuffer, buf, nbyte);
+    if (n > 0) {
+        spice_buffer_remove(&s->sasl.inbuffer, n);
+        if (n == nbyte)
+            return n;
+        nbyte -= n;
+        buf += n;
+    }
+
+    n = s->read(s, encoded, sizeof(encoded));
+    if (n <= 0)
+        return n;
+
+    err = sasl_decode(s->sasl.conn,
+                      (char *)encoded, n,
+                      &decoded, &decodedlen);
+    if (err != SASL_OK) {
+        red_printf("sasl_decode error: %d", err);
+        return -1;
+    }
+
+    if (decodedlen == 0) {
+        errno = EAGAIN;
+        return -1;
+    }
+
+    n = MIN(nbyte, decodedlen);
+    memcpy(buf, decoded, n);
+    spice_buffer_append(&s->sasl.inbuffer, decoded + n, decodedlen - n);
+    return n;
+}
+#endif
+
 static void async_read_handler(int fd, int event, void *data)
 {
     AsyncRead *obj = (AsyncRead *)data;
@@ -1723,16 +1828,531 @@ static void reds_get_spice_ticket(RedLinkInfo *link)
     async_read_handler(0, 0, &link->asyc_read);
 }
 
+#if HAVE_SASL
+static char *addr_to_string(const char *format,
+                            struct sockaddr *sa,
+                            socklen_t salen) {
+    char *addr;
+    char host[NI_MAXHOST];
+    char serv[NI_MAXSERV];
+    int err;
+    size_t addrlen;
+
+    if ((err = getnameinfo(sa, salen,
+                           host, sizeof(host),
+                           serv, sizeof(serv),
+                           NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
+        red_printf("Cannot resolve address %d: %s",
+                   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 = spice_malloc(addrlen + 1);
+    snprintf(addr, addrlen, format, host, serv);
+    addr[addrlen] = '\0';
+
+    return addr;
+}
+
+static int auth_sasl_check_ssf(RedsSASLContext *sasl, int *runSSF)
+{
+    const void *val;
+    int err, ssf;
+
+    *runSSF = 0;
+    if (!sasl->wantSSF)
+        return 1;
+
+    err = sasl_getprop(sasl->conn, SASL_SSF, &val);
+    if (err != SASL_OK)
+        return 0;
+
+    ssf = *(const int *)val;
+    red_printf("negotiated an SSF of %d", ssf);
+    if (ssf < 56)
+        return 0; /* 56 is good for Kerberos */
+
+    *runSSF = 1;
+
+    /* We have a SSF that's good enough */
+    return 1;
+}
+
+/*
+ * Step Msg
+ *
+ * Input from client:
+ *
+ * u32 clientin-length
+ * u8-array clientin-string
+ *
+ * Output to client:
+ *
+ * u32 serverout-length
+ * u8-array serverout-strin
+ * u8 continue
+ */
+#define SASL_DATA_MAX_LEN (1024 * 1024)
+
+static void reds_handle_auth_sasl_steplen(void *opaque);
+
+static void reds_handle_auth_sasl_step(void *opaque)
+{
+    const char *serverout;
+    unsigned int serveroutlen;
+    int err;
+    char *clientdata = NULL;
+    RedLinkInfo *link = (RedLinkInfo *)opaque;
+    RedsSASLContext *sasl = &link->stream->sasl;
+    uint32_t datalen = sasl->len;
+    AsyncRead *obj = &link->asyc_read;
+
+    /* NB, distinction of NULL vs "" is *critical* in SASL */
+    if (datalen) {
+        clientdata = sasl->data;
+        clientdata[datalen - 1] = '\0'; /* Wire includes '\0', but make sure */
+        datalen--; /* Don't count NULL byte when passing to _start() */
+    }
+
+    red_printf("Step using SASL Data %p (%d bytes)",
+               clientdata, datalen);
+    err = sasl_server_step(sasl->conn,
+                           clientdata,
+                           datalen,
+                           &serverout,
+                           &serveroutlen);
+    if (err != SASL_OK &&
+        err != SASL_CONTINUE) {
+        red_printf("sasl step failed %d (%s)",
+                   err, sasl_errdetail(sasl->conn));
+        goto authabort;
+    }
+
+    if (serveroutlen > SASL_DATA_MAX_LEN) {
+        red_printf("sasl step reply data too long %d",
+                   serveroutlen);
+        goto authabort;
+    }
+
+    red_printf("SASL return data %d bytes, %p", serveroutlen, serverout);
+
+    if (serveroutlen) {
+        serveroutlen += 1;
+        sync_write(link->stream, &serveroutlen, sizeof(uint32_t));
+        sync_write(link->stream, serverout, serveroutlen);
+    } else {
+        sync_write(link->stream, &serveroutlen, sizeof(uint32_t));
+    }
+
+    /* Whether auth is complete */
+    sync_write_u8(link->stream, err == SASL_CONTINUE ? 0 : 1);
+
+    if (err == SASL_CONTINUE) {
+        red_printf("%s", "Authentication must continue (step)");
+        /* Wait for step length */
+        obj->now = (uint8_t *)&sasl->len;
+        obj->end = obj->now + sizeof(uint32_t);
+        obj->done = reds_handle_auth_sasl_steplen;
+        async_read_handler(0, 0, &link->asyc_read);
+    } else {
+        int ssf;
+
+        if (auth_sasl_check_ssf(sasl, &ssf) == 0) {
+            red_printf("Authentication rejected for weak SSF");
+            goto authreject;
+        }
+
+        red_printf("Authentication successful");
+        sync_write_u32(link->stream, SPICE_LINK_ERR_OK); /* Accept auth */
+
+        /*
+         * Delay writing in SSF encoded until now
+         */
+        sasl->runSSF = ssf;
+        link->stream->writev = NULL; /* make sure writev isn't called directly anymore */
+
+        reds_handle_link(link);
+    }
+
+    return;
+
+authreject:
+    sync_write_u32(link->stream, 1); /* Reject auth */
+    sync_write_u32(link->stream, sizeof("Authentication failed"));
+    sync_write(link->stream, "Authentication failed", sizeof("Authentication failed"));
+
+authabort:
+    reds_link_free(link);
+    return;
+}
+
+static void reds_handle_auth_sasl_steplen(void *opaque)
+{
+    RedLinkInfo *link = (RedLinkInfo *)opaque;
+    AsyncRead *obj = &link->asyc_read;
+    RedsSASLContext *sasl = &link->stream->sasl;
+
+    red_printf("Got steplen %d", sasl->len);
+    if (sasl->len > SASL_DATA_MAX_LEN) {
+        red_printf("Too much SASL data %d", sasl->len);
+        reds_link_free(link);
+        return;
+    }
+
+    if (sasl->len == 0)
+        return reds_handle_auth_sasl_step(opaque);
+    else {
+        sasl->data = spice_realloc(sasl->data, sasl->len);
+        obj->now = (uint8_t *)sasl->data;
+        obj->end = obj->now + sasl->len;
+        obj->done = reds_handle_auth_sasl_step;
+        async_read_handler(0, 0, &link->asyc_read);
+    }
+}
+
+/*
+ * Start Msg
+ *
+ * Input from client:
+ *
+ * u32 clientin-length
+ * u8-array clientin-string
+ *
+ * Output to client:
+ *
+ * u32 serverout-length
+ * u8-array serverout-strin
+ * u8 continue
+ */
+
+
+static void reds_handle_auth_sasl_start(void *opaque)
+{
+    RedLinkInfo *link = (RedLinkInfo *)opaque;
+    AsyncRead *obj = &link->asyc_read;
+    const char *serverout;
+    unsigned int serveroutlen;
+    int err;
+    char *clientdata = NULL;
+    RedsSASLContext *sasl = &link->stream->sasl;
+    uint32_t datalen = sasl->len;
+
+    /* NB, distinction of NULL vs "" is *critical* in SASL */
+    if (datalen) {
+        clientdata = sasl->data;
+        clientdata[datalen - 1] = '\0'; /* Should be on wire, but make sure */
+        datalen--; /* Don't count NULL byte when passing to _start() */
+    }
+
+    red_printf("Start SASL auth with mechanism %s. Data %p (%d bytes)",
+              sasl->mechlist, clientdata, datalen);
+    err = sasl_server_start(sasl->conn,
+                            sasl->mechlist,
+                            clientdata,
+                            datalen,
+                            &serverout,
+                            &serveroutlen);
+    if (err != SASL_OK &&
+        err != SASL_CONTINUE) {
+        red_printf("sasl start failed %d (%s)",
+                   err, sasl_errdetail(sasl->conn));
+        goto authabort;
+    }
+
+    if (serveroutlen > SASL_DATA_MAX_LEN) {
+        red_printf("sasl start reply data too long %d",
+                   serveroutlen);
+        goto authabort;
+    }
+
+    red_printf("SASL return data %d bytes, %p", serveroutlen, serverout);
+
+    if (serveroutlen) {
+        serveroutlen += 1;
+        sync_write(link->stream, &serveroutlen, sizeof(uint32_t));
+        sync_write(link->stream, serverout, serveroutlen);
+    } else {
+        sync_write(link->stream, &serveroutlen, sizeof(uint32_t));
+    }
+
+    /* Whether auth is complete */
+    sync_write_u8(link->stream, err == SASL_CONTINUE ? 0 : 1);
+
+    if (err == SASL_CONTINUE) {
+        red_printf("%s", "Authentication must continue (start)");
+        /* Wait for step length */
+        obj->now = (uint8_t *)&sasl->len;
+        obj->end = obj->now + sizeof(uint32_t);
+        obj->done = reds_handle_auth_sasl_steplen;
+        async_read_handler(0, 0, &link->asyc_read);
+    } else {
+        int ssf;
+
+        if (auth_sasl_check_ssf(sasl, &ssf) == 0) {
+            red_printf("Authentication rejected for weak SSF");
+            goto authreject;
+        }
+
+        red_printf("Authentication successful");
+        sync_write_u32(link->stream, SPICE_LINK_ERR_OK); /* Accept auth */
+
+        /*
+         * Delay writing in SSF encoded until now
+         */
+        sasl->runSSF = ssf;
+        link->stream->writev = NULL; /* make sure writev isn't called directly anymore */
+
+        reds_handle_link(link);
+    }
+
+    return;
+
+authreject:
+    sync_write_u32(link->stream, 1); /* Reject auth */
+    sync_write_u32(link->stream, sizeof("Authentication failed"));
+    sync_write(link->stream, "Authentication failed", sizeof("Authentication failed"));
+
+authabort:
+    reds_link_free(link);
+    return;
+}
+
+static void reds_handle_auth_startlen(void *opaque)
+{
+    RedLinkInfo *link = (RedLinkInfo *)opaque;
+    AsyncRead *obj = &link->asyc_read;
+    RedsSASLContext *sasl = &link->stream->sasl;
+
+    red_printf("Got client start len %d", sasl->len);
+    if (sasl->len > SASL_DATA_MAX_LEN) {
+        red_printf("Too much SASL data %d", sasl->len);
+        reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
+        reds_link_free(link);
+        return;
+    }
+
+    if (sasl->len == 0) {
+        reds_handle_auth_sasl_start(opaque);
+        return;
+    }
+
+    sasl->data = spice_realloc(sasl->data, sasl->len);
+    obj->now = (uint8_t *)sasl->data;
+    obj->end = obj->now + sasl->len;
+    obj->done = reds_handle_auth_sasl_start;
+    async_read_handler(0, 0, &link->asyc_read);
+}
+
+static void reds_handle_auth_mechname(void *opaque)
+{
+    RedLinkInfo *link = (RedLinkInfo *)opaque;
+    AsyncRead *obj = &link->asyc_read;
+    RedsSASLContext *sasl = &link->stream->sasl;
+
+    sasl->mechname[sasl->len] = '\0';
+    red_printf("Got client mechname '%s' check against '%s'",
+               sasl->mechname, sasl->mechlist);
+
+    if (strncmp(sasl->mechlist, sasl->mechname, sasl->len) == 0) {
+        if (sasl->mechlist[sasl->len] != '\0' &&
+            sasl->mechlist[sasl->len] != ',') {
+            red_printf("One %d", sasl->mechlist[sasl->len]);
+            reds_link_free(link);
+            return;
+        }
+    } else {
+        char *offset = strstr(sasl->mechlist, sasl->mechname);
+        red_printf("Two %p", offset);
+        if (!offset) {
+            reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
+            return;
+        }
+        red_printf("Two '%s'", offset);
+        if (offset[-1] != ',' ||
+            (offset[sasl->len] != '\0'&&
+             offset[sasl->len] != ',')) {
+            reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
+            return;
+        }
+    }
+
+    free(sasl->mechlist);
+    sasl->mechlist = strdup(sasl->mechname);
+
+    red_printf("Validated mechname '%s'", sasl->mechname);
+
+    obj->now = (uint8_t *)&sasl->len;
+    obj->end = obj->now + sizeof(uint32_t);
+    obj->done = reds_handle_auth_startlen;
+    async_read_handler(0, 0, &link->asyc_read);
+
+    return;
+}
+
+static void reds_handle_auth_mechlen(void *opaque)
+{
+    RedLinkInfo *link = (RedLinkInfo *)opaque;
+    AsyncRead *obj = &link->asyc_read;
+    RedsSASLContext *sasl = &link->stream->sasl;
+
+    if (sasl->len < 1 || sasl->len > 100) {
+        red_printf("Got client mechname len %d", sasl->len);
+        reds_link_free(link);
+        return;
+    }
+
+    sasl->mechname = spice_malloc(sasl->len + 1);
+
+    red_printf("Wait for client mechname");
+    obj->now = (uint8_t *)sasl->mechname;
+    obj->end = obj->now + sasl->len;
+    obj->done = reds_handle_auth_mechname;
+    async_read_handler(0, 0, &link->asyc_read);
+}
+
+static void reds_start_auth_sasl(RedLinkInfo *link)
+{
+    const char *mechlist = NULL;
+    sasl_security_properties_t secprops;
+    int err;
+    char *localAddr, *remoteAddr;
+    int mechlistlen;
+    AsyncRead *obj = &link->asyc_read;
+    RedsSASLContext *sasl = &link->stream->sasl;
+
+    /* Get local & remote client addresses in form  IPADDR;PORT */
+    if (!(localAddr = addr_to_string("%s;%s", &link->stream->info.laddr, link->stream->info.llen)))
+        goto error;
+
+    if (!(remoteAddr = addr_to_string("%s;%s", &link->stream->info.paddr, link->stream->info.plen))) {
+        free(localAddr);
+        goto error;
+    }
+
+    err = sasl_server_new("spice",
+                          NULL, /* FQDN - just delegates to gethostname */
+                          NULL, /* User realm */
+                          localAddr,
+                          remoteAddr,
+                          NULL, /* Callbacks, not needed */
+                          SASL_SUCCESS_DATA,
+                          &sasl->conn);
+    free(localAddr);
+    free(remoteAddr);
+    localAddr = remoteAddr = NULL;
+
+    if (err != SASL_OK) {
+        red_printf("sasl context setup failed %d (%s)",
+                   err, sasl_errstring(err, NULL, NULL));
+        sasl->conn = NULL;
+        goto error;
+    }
+
+    /* Inform SASL that we've got an external SSF layer from TLS */
+    if (link->stream->ssl) {
+        sasl_ssf_t ssf;
+
+        ssf = SSL_get_cipher_bits(link->stream->ssl, NULL);
+        err = sasl_setprop(sasl->conn, SASL_SSF_EXTERNAL, &ssf);
+        if (err != SASL_OK) {
+            red_printf("cannot set SASL external SSF %d (%s)",
+                       err, sasl_errstring(err, NULL, NULL));
+            sasl_dispose(&sasl->conn);
+            sasl->conn = NULL;
+            goto error;
+        }
+    } else
+        sasl->wantSSF = 1;
+
+    memset(&secprops, 0, sizeof secprops);
+    /* Inform SASL that we've got an external SSF layer from TLS */
+    if (link->stream->ssl) {
+        /* If we've got TLS (or UNIX domain sock), we don't care about SSF */
+        secprops.min_ssf = 0;
+        secprops.max_ssf = 0;
+        secprops.maxbufsize = 8192;
+        secprops.security_flags = 0;
+    } else {
+        /* Plain TCP, better get an SSF layer */
+        secprops.min_ssf = 56; /* Good enough to require kerberos */
+        secprops.max_ssf = 100000; /* Arbitrary big number */
+        secprops.maxbufsize = 8192;
+        /* Forbid any anonymous or trivially crackable auth */
+        secprops.security_flags =
+            SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
+    }
+
+    err = sasl_setprop(sasl->conn, SASL_SEC_PROPS, &secprops);
+    if (err != SASL_OK) {
+        red_printf("cannot set SASL security props %d (%s)",
+                   err, sasl_errstring(err, NULL, NULL));
+        sasl_dispose(&sasl->conn);
+        sasl->conn = NULL;
+        goto error;
+    }
+
+    err = sasl_listmech(sasl->conn,
+                        NULL, /* Don't need to set user */
+                        "", /* Prefix */
+                        ",", /* Separator */
+                        "", /* Suffix */
+                        &mechlist,
+                        NULL,
+                        NULL);
+    if (err != SASL_OK) {
+        red_printf("cannot list SASL mechanisms %d (%s)",
+                   err, sasl_errdetail(sasl->conn));
+        sasl_dispose(&sasl->conn);
+        sasl->conn = NULL;
+        goto error;
+    }
+    red_printf("Available mechanisms for client: '%s'", mechlist);
+
+    sasl->mechlist = strdup(mechlist);
+
+    mechlistlen = strlen(mechlist);
+    if (!sync_write(link->stream, &mechlistlen, sizeof(uint32_t))
+        || !sync_write(link->stream, sasl->mechlist, mechlistlen)) {
+        red_printf("SASL mechanisms write error");
+        goto error;
+    }
+
+    red_printf("Wait for client mechname length");
+    obj->now = (uint8_t *)&sasl->len;
+    obj->end = obj->now + sizeof(uint32_t);
+    obj->done = reds_handle_auth_mechlen;
+    async_read_handler(0, 0, &link->asyc_read);
+
+    return;
+
+error:
+    reds_link_free(link);
+    return;
+}
+#endif
+
 static void reds_handle_auth_mechanism(void *opaque)
 {
     RedLinkInfo *link = (RedLinkInfo *)opaque;
 
     red_printf("Auth method: %d", link->auth_mechanism.auth_mechanism);
 
-    if (link->auth_mechanism.auth_mechanism == SPICE_COMMON_CAP_AUTH_SPICE) {
+    if (link->auth_mechanism.auth_mechanism == SPICE_COMMON_CAP_AUTH_SPICE
+        && !sasl_enabled
+        ) {
         reds_get_spice_ticket(link);
+#if HAVE_SASL
+    } else if (link->auth_mechanism.auth_mechanism == SPICE_COMMON_CAP_AUTH_SASL) {
+        red_printf("Starting SASL");
+        reds_start_auth_sasl(link);
+#endif
     } else {
         red_printf("Unknown auth method, disconnecting");
+        if (sasl_enabled)
+            red_printf("Your client doesn't handle SASL?");
         reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
         reds_link_free(link);
     }
@@ -1784,6 +2404,11 @@ static void reds_handle_read_link_done(void *opaque)
     }
 
     if (!auth_selection) {
+        if (sasl_enabled) {
+            red_printf("SASL enabled, but peer supports only spice authentication");
+            reds_send_link_error(link, SPICE_LINK_ERR_VERSION_MISMATCH);
+            return;
+        }
         red_printf("Peer doesn't support AUTH selection");
         reds_get_spice_ticket(link);
     } else {
@@ -2846,6 +3471,16 @@ static int do_spice_init(SpiceCoreInterface *core_interface)
     if (reds->secure_listen_socket != -1) {
         reds_init_ssl();
     }
+#if HAVE_SASL
+    int saslerr;
+    if ((saslerr = sasl_server_init(NULL, sasl_appname ?
+                                    sasl_appname : "spice")) != SASL_OK) {
+        red_error("Failed to initialize SASL auth %s",
+                  sasl_errstring(saslerr, NULL, NULL));
+        goto err;
+    }
+#endif
+
     reds->main_channel = NULL;
     inputs_init();
 
@@ -2939,6 +3574,29 @@ __visible__ int spice_server_set_noauth(SpiceServer *s)
     return 0;
 }
 
+__visible__ int spice_server_set_sasl(SpiceServer *s, int enabled)
+{
+    ASSERT(reds == s);
+#if HAVE_SASL
+    sasl_enabled = enabled;
+    return 0;
+#else
+    return -1;
+#endif
+}
+
+__visible__ int spice_server_set_sasl_appname(SpiceServer *s, const char *appname)
+{
+    ASSERT(reds == s);
+#if HAVE_SASL
+    free(sasl_appname);
+    sasl_appname = strdup(appname);
+    return 0;
+#else
+    return -1;
+#endif
+}
+
 __visible__ int spice_server_set_ticket(SpiceServer *s,
                                         const char *passwd, int lifetime,
                                         int fail_if_connected,
@@ -3214,12 +3872,30 @@ __visible__ int spice_server_migrate_switch(SpiceServer *s)
 
 ssize_t reds_stream_read(RedsStream *s, void *buf, size_t nbyte)
 {
-    return s->read(s, buf, nbyte);
+    ssize_t ret;
+
+#if HAVE_SASL
+    if (s->sasl.conn && s->sasl.runSSF)
+        ret = reds_stream_sasl_read(s, buf, nbyte);
+    else
+#endif
+        ret = s->read(s, buf, nbyte);
+
+    return ret;
 }
 
 ssize_t reds_stream_write(RedsStream *s, const void *buf, size_t nbyte)
 {
-    return s->write(s, buf, nbyte);
+    ssize_t ret;
+
+#if HAVE_SASL
+    if (s->sasl.conn && s->sasl.runSSF)
+        ret = reds_stream_sasl_write(s, buf, nbyte);
+    else
+#endif
+        ret = s->write(s, buf, nbyte);
+
+    return ret;
 }
 
 ssize_t reds_stream_writev(RedsStream *s, const struct iovec *iov, int iovcnt)
@@ -3248,6 +3924,20 @@ void reds_stream_free(RedsStream *s)
 
     spice_channel_event(&s->info, SPICE_CHANNEL_EVENT_DISCONNECTED);
 
+#if HAVE_SASL
+    if (s->sasl.conn) {
+        s->sasl.runSSF = s->sasl.wantSSF = 0;
+        s->sasl.len = 0;
+        s->sasl.encodedLength = s->sasl.encodedOffset = 0;
+        s->sasl.encoded = NULL;
+        free(s->sasl.mechlist);
+        free(s->sasl.mechname);
+        s->sasl.mechlist = NULL;
+        sasl_dispose(&s->sasl.conn);
+        s->sasl.conn = NULL;
+    }
+#endif
+
     if (s->ssl)
         SSL_free(s->ssl);
 
diff --git a/server/reds.h b/server/reds.h
index f0276b1..ff015b2 100644
--- a/server/reds.h
+++ b/server/reds.h
@@ -18,10 +18,16 @@
 #ifndef _H_REDS
 #define _H_REDS
 
+#include "config.h"
+
 #include <stdint.h>
 #include <openssl/ssl.h>
 #include <sys/uio.h>
 #include <spice/vd_agent.h>
+#if HAVE_SASL
+#include <sasl/sasl.h>
+#endif
+
 #include "common/marshaller.h"
 #include "common/messages.h"
 #include "spice.h"
@@ -30,6 +36,35 @@
 
 typedef struct RedsStream RedsStream;
 
+#if HAVE_SASL
+typedef struct RedsSASLContext {
+    sasl_conn_t *conn;
+
+    /* If we want to negotiate an SSF layer with client */
+    int wantSSF :1;
+    /* If we are now running the SSF layer */
+    int runSSF :1;
+
+    /*
+     * Buffering encoded data to allow more clear data
+     * to be stuffed onto the output buffer
+     */
+    const uint8_t *encoded;
+    unsigned int encodedLength;
+    unsigned int encodedOffset;
+
+    SpiceBuffer inbuffer;
+
+    char *username;
+    char *mechlist;
+    char *mechname;
+
+    /* temporary data during authentication */
+    unsigned int len;
+    char *data;
+} RedsSASLContext;
+#endif
+
 struct RedsStream {
     int socket;
     SpiceWatch *watch;
@@ -39,6 +74,10 @@ struct RedsStream {
     int shutdown;
     SSL *ssl;
 
+#if HAVE_SASL
+    RedsSASLContext sasl;
+#endif
+
     SpiceChannelEventInfo info;
 
     /* private */
diff --git a/server/spice.h b/server/spice.h
index 6fb22a4..7e85ad7 100644
--- a/server/spice.h
+++ b/server/spice.h
@@ -374,6 +374,8 @@ int spice_server_set_compat_version(SpiceServer *s,
 int spice_server_set_port(SpiceServer *s, int port);
 void spice_server_set_addr(SpiceServer *s, const char *addr, int flags);
 int spice_server_set_noauth(SpiceServer *s);
+int spice_server_set_sasl(SpiceServer *s, int enabled);
+int spice_server_set_sasl_appname(SpiceServer *s, const char *appname);
 int spice_server_set_ticket(SpiceServer *s, const char *passwd, int lifetime,
                             int fail_if_connected, int disconnect_if_connected);
 int spice_server_set_tls(SpiceServer *s, int port,
-- 
1.7.4



More information about the Spice-devel mailing list