summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristophe Fergeau <cfergeau@redhat.com>2013-10-08 11:11:37 +0200
committerChristophe Fergeau <cfergeau@redhat.com>2014-01-20 12:15:42 +0100
commit7ff743c43141739286aabcf08f3aa37e8e333a49 (patch)
treece4a760112c5755db1f8e86d8788279b16d710c1
parent9feed6940ffda3171883a366a48693e8df6c5338 (diff)
downloadspice-7ff743c43141739286aabcf08f3aa37e8e333a49.tar.gz
spice-7ff743c43141739286aabcf08f3aa37e8e333a49.tar.xz
spice-7ff743c43141739286aabcf08f3aa37e8e333a49.zip
Move SASL authentication to reds_stream.h
SASL authentication mostly use members from RedsStream to do its work, so it makes sense to have its code in reds_stream.c. This should allow to make RedsStream::sasl private in the future.
-rw-r--r--server/reds.c458
-rw-r--r--server/reds_stream.c510
-rw-r--r--server/reds_stream.h18
3 files changed, 563 insertions, 423 deletions
diff --git a/server/reds.c b/server/reds.c
index 15291c53..252cf5be 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -1882,61 +1882,6 @@ static void reds_get_spice_ticket(RedLinkInfo *link)
}
#if HAVE_SASL
-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) {
- spice_warning("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(RedsSASL *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;
- spice_info("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
*
@@ -1957,115 +1902,25 @@ 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;
- RedsSASL *sasl = &link->stream->sasl;
- uint32_t datalen = sasl->len;
- AsyncRead *obj = &link->async_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() */
- }
-
- spice_info("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) {
- spice_warning("sasl step failed %d (%s)",
- err, sasl_errdetail(sasl->conn));
- goto authabort;
- }
-
- if (serveroutlen > SASL_DATA_MAX_LEN) {
- spice_warning("sasl step reply data too long %d",
- serveroutlen);
- goto authabort;
- }
-
- spice_info("SASL return data %d bytes, %p", serveroutlen, serverout);
-
- if (serveroutlen) {
- serveroutlen += 1;
- reds_stream_write_all(link->stream, &serveroutlen, sizeof(uint32_t));
- reds_stream_write_all(link->stream, serverout, serveroutlen);
- } else {
- reds_stream_write_all(link->stream, &serveroutlen, sizeof(uint32_t));
- }
-
- /* Whether auth is complete */
- reds_stream_write_u8(link->stream, err == SASL_CONTINUE ? 0 : 1);
-
- if (err == SASL_CONTINUE) {
- spice_info("%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->async_read);
- } else {
- int ssf;
-
- if (auth_sasl_check_ssf(sasl, &ssf) == 0) {
- spice_warning("Authentication rejected for weak SSF");
- goto authreject;
- }
-
- spice_info("Authentication successful");
- reds_stream_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 */
+ RedsSaslError status;
+ status = reds_sasl_handle_auth_start(link->stream, reds_handle_auth_sasl_steplen, link);
+ if (status == REDS_SASL_ERROR_OK) {
reds_handle_link(link);
+ } else if (status != REDS_SASL_ERROR_CONTINUE) {
+ reds_link_free(link);
}
-
- return;
-
-authreject:
- reds_stream_write_u32(link->stream, 1); /* Reject auth */
- reds_stream_write_u32(link->stream, sizeof("Authentication failed"));
- reds_stream_write_all(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->async_read;
- RedsSASL *sasl = &link->stream->sasl;
+ RedsSaslError status;
- spice_info("Got steplen %d", sasl->len);
- if (sasl->len > SASL_DATA_MAX_LEN) {
- spice_warning("Too much SASL data %d", sasl->len);
+ status = reds_sasl_handle_auth_steplen(link->stream, reds_handle_auth_sasl_step, link);
+ if (status != REDS_SASL_ERROR_OK) {
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->async_read);
}
}
@@ -2088,307 +1943,64 @@ static void reds_handle_auth_sasl_steplen(void *opaque)
static void reds_handle_auth_sasl_start(void *opaque)
{
RedLinkInfo *link = (RedLinkInfo *)opaque;
- AsyncRead *obj = &link->async_read;
- const char *serverout;
- unsigned int serveroutlen;
- int err;
- char *clientdata = NULL;
- RedsSASL *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() */
- }
-
- spice_info("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) {
- spice_warning("sasl start failed %d (%s)",
- err, sasl_errdetail(sasl->conn));
- goto authabort;
- }
-
- if (serveroutlen > SASL_DATA_MAX_LEN) {
- spice_warning("sasl start reply data too long %d",
- serveroutlen);
- goto authabort;
- }
-
- spice_info("SASL return data %d bytes, %p", serveroutlen, serverout);
-
- if (serveroutlen) {
- serveroutlen += 1;
- reds_stream_write_all(link->stream, &serveroutlen, sizeof(uint32_t));
- reds_stream_write_all(link->stream, serverout, serveroutlen);
- } else {
- reds_stream_write_all(link->stream, &serveroutlen, sizeof(uint32_t));
- }
-
- /* Whether auth is complete */
- reds_stream_write_u8(link->stream, err == SASL_CONTINUE ? 0 : 1);
-
- if (err == SASL_CONTINUE) {
- spice_info("%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->async_read);
- } else {
- int ssf;
-
- if (auth_sasl_check_ssf(sasl, &ssf) == 0) {
- spice_warning("Authentication rejected for weak SSF");
- goto authreject;
- }
-
- spice_info("Authentication successful");
- reds_stream_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 */
+ RedsSaslError status;
+ status = reds_sasl_handle_auth_start(link->stream, reds_handle_auth_sasl_steplen, link);
+ if (status == REDS_SASL_ERROR_OK) {
reds_handle_link(link);
+ } else if (status != REDS_SASL_ERROR_CONTINUE) {
+ reds_link_free(link);
}
-
- return;
-
-authreject:
- reds_stream_write_u32(link->stream, 1); /* Reject auth */
- reds_stream_write_u32(link->stream, sizeof("Authentication failed"));
- reds_stream_write_all(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->async_read;
- RedsSASL *sasl = &link->stream->sasl;
+ RedsSaslError status;
- spice_info("Got client start len %d", sasl->len);
- if (sasl->len > SASL_DATA_MAX_LEN) {
- spice_warning("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;
+ status = reds_sasl_handle_auth_startlen(link->stream, reds_handle_auth_sasl_start, link);
+ switch (status) {
+ case REDS_SASL_ERROR_OK:
+ break;
+ case REDS_SASL_ERROR_RETRY:
+ reds_handle_auth_sasl_start(opaque);
+ break;
+ case REDS_SASL_ERROR_GENERIC:
+ case REDS_SASL_ERROR_INVALID_DATA:
+ reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
+ reds_link_free(link);
+ break;
+ default:
+ g_warn_if_reached();
+ reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
+ reds_link_free(link);
+ break;
}
-
- 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->async_read);
}
static void reds_handle_auth_mechname(void *opaque)
{
RedLinkInfo *link = (RedLinkInfo *)opaque;
- AsyncRead *obj = &link->async_read;
- RedsSASL *sasl = &link->stream->sasl;
- sasl->mechname[sasl->len] = '\0';
- spice_info("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] != ',') {
- spice_info("One %d", sasl->mechlist[sasl->len]);
- reds_link_free(link);
- return;
- }
- } else {
- char *offset = strstr(sasl->mechlist, sasl->mechname);
- spice_info("Two %p", offset);
- if (!offset) {
+ if (!reds_sasl_handle_auth_mechname(link->stream, reds_handle_auth_startlen, link)) {
reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
- return;
- }
- spice_info("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 = spice_strdup(sasl->mechname);
-
- spice_info("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->async_read);
-
- return;
}
static void reds_handle_auth_mechlen(void *opaque)
{
RedLinkInfo *link = (RedLinkInfo *)opaque;
- AsyncRead *obj = &link->async_read;
- RedsSASL *sasl = &link->stream->sasl;
- if (sasl->len < 1 || sasl->len > 100) {
- spice_warning("Got bad client mechname len %d", sasl->len);
+ if (!reds_sasl_handle_auth_mechlen(link->stream, reds_handle_auth_mechname, link)) {
reds_link_free(link);
- return;
}
-
- sasl->mechname = spice_malloc(sasl->len + 1);
-
- spice_info("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->async_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->async_read;
- RedsSASL *sasl = &link->stream->sasl;
-
- /* Get local & remote client addresses in form IPADDR;PORT */
- if (!(localAddr = addr_to_string("%s;%s", &link->stream->info->laddr_ext,
- link->stream->info->llen_ext))) {
- goto error;
- }
-
- if (!(remoteAddr = addr_to_string("%s;%s", &link->stream->info->paddr_ext,
- link->stream->info->plen_ext))) {
- 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) {
- spice_warning("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) {
- spice_warning("cannot set SASL external SSF %d (%s)",
- err, sasl_errstring(err, NULL, NULL));
- goto error_dispose;
- }
- } 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) {
- spice_warning("cannot set SASL security props %d (%s)",
- err, sasl_errstring(err, NULL, NULL));
- goto error_dispose;
- }
-
- err = sasl_listmech(sasl->conn,
- NULL, /* Don't need to set user */
- "", /* Prefix */
- ",", /* Separator */
- "", /* Suffix */
- &mechlist,
- NULL,
- NULL);
- if (err != SASL_OK || mechlist == NULL) {
- spice_warning("cannot list SASL mechanisms %d (%s)",
- err, sasl_errdetail(sasl->conn));
- goto error_dispose;
- }
-
- spice_info("Available mechanisms for client: '%s'", mechlist);
-
- sasl->mechlist = spice_strdup(mechlist);
-
- mechlistlen = strlen(mechlist);
- if (!reds_stream_write_all(link->stream, &mechlistlen, sizeof(uint32_t))
- || !reds_stream_write_all(link->stream, sasl->mechlist, mechlistlen)) {
- spice_warning("SASL mechanisms write error");
- goto error;
+ if (!reds_sasl_start_auth(link->stream, reds_handle_auth_mechlen, link)) {
+ reds_link_free(link);
}
-
- spice_info("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->async_read);
-
- return;
-
-error_dispose:
- sasl_dispose(&sasl->conn);
- sasl->conn = NULL;
-error:
- reds_link_free(link);
- return;
}
#endif
diff --git a/server/reds_stream.c b/server/reds_stream.c
index f4513e6c..2703ec75 100644
--- a/server/reds_stream.c
+++ b/server/reds_stream.c
@@ -251,6 +251,11 @@ RedsStream *reds_stream_new(int socket)
return stream;
}
+void reds_stream_disable_writev(RedsStream *stream)
+{
+ stream->writev = NULL;
+}
+
RedsStreamSslStatus reds_stream_ssl_accept(RedsStream *stream)
{
int ssl_error;
@@ -463,4 +468,509 @@ static ssize_t reds_stream_sasl_read(RedsStream *s, uint8_t *buf, size_t nbyte)
spice_buffer_append(&s->sasl.inbuffer, decoded + n, decodedlen - n);
return n;
}
+
+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) {
+ spice_warning("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 char *reds_stream_get_local_address(RedsStream *stream)
+{
+ return addr_to_string("%s;%s", &stream->info->laddr_ext,
+ stream->info->llen_ext);
+}
+
+static char *reds_stream_get_remote_address(RedsStream *stream)
+{
+ return addr_to_string("%s;%s", &stream->info->paddr_ext,
+ stream->info->plen_ext);
+}
+
+static int auth_sasl_check_ssf(RedsSASL *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;
+ spice_info("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)
+
+RedsSaslError reds_sasl_handle_auth_step(RedsStream *stream, AsyncReadDone read_cb, void *opaque)
+{
+ const char *serverout;
+ unsigned int serveroutlen;
+ int err;
+ char *clientdata = NULL;
+ RedsSASL *sasl = &stream->sasl;
+ uint32_t datalen = sasl->len;
+ AsyncRead *obj = &stream->async_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() */
+ }
+
+ spice_info("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) {
+ spice_warning("sasl step failed %d (%s)",
+ err, sasl_errdetail(sasl->conn));
+ return REDS_SASL_ERROR_GENERIC;
+ }
+
+ if (serveroutlen > SASL_DATA_MAX_LEN) {
+ spice_warning("sasl step reply data too long %d",
+ serveroutlen);
+ return REDS_SASL_ERROR_INVALID_DATA;
+ }
+
+ spice_info("SASL return data %d bytes, %p", serveroutlen, serverout);
+
+ if (serveroutlen) {
+ serveroutlen += 1;
+ reds_stream_write_all(stream, &serveroutlen, sizeof(uint32_t));
+ reds_stream_write_all(stream, serverout, serveroutlen);
+ } else {
+ reds_stream_write_all(stream, &serveroutlen, sizeof(uint32_t));
+ }
+
+ /* Whether auth is complete */
+ reds_stream_write_u8(stream, err == SASL_CONTINUE ? 0 : 1);
+
+ if (err == SASL_CONTINUE) {
+ spice_info("%s", "Authentication must continue (step)");
+ /* Wait for step length */
+ obj->now = (uint8_t *)&sasl->len;
+ obj->end = obj->now + sizeof(uint32_t);
+ obj->done = read_cb;
+ async_read_handler(0, 0, &stream->async_read);
+ return REDS_SASL_ERROR_CONTINUE;
+ } else {
+ int ssf;
+
+ if (auth_sasl_check_ssf(sasl, &ssf) == 0) {
+ spice_warning("Authentication rejected for weak SSF");
+ goto authreject;
+ }
+
+ spice_info("Authentication successful");
+ reds_stream_write_u32(stream, SPICE_LINK_ERR_OK); /* Accept auth */
+
+ /*
+ * Delay writing in SSF encoded until now
+ */
+ sasl->runSSF = ssf;
+ reds_stream_disable_writev(stream); /* make sure writev isn't called directly anymore */
+
+ return REDS_SASL_ERROR_OK;
+ }
+
+authreject:
+ reds_stream_write_u32(stream, 1); /* Reject auth */
+ reds_stream_write_u32(stream, sizeof("Authentication failed"));
+ reds_stream_write_all(stream, "Authentication failed", sizeof("Authentication failed"));
+
+ return REDS_SASL_ERROR_AUTH_FAILED;
+}
+
+RedsSaslError reds_sasl_handle_auth_steplen(RedsStream *stream, AsyncReadDone read_cb, void *opaque)
+{
+ AsyncRead *obj = &stream->async_read;
+ RedsSASL *sasl = &stream->sasl;
+
+ spice_info("Got steplen %d", sasl->len);
+ if (sasl->len > SASL_DATA_MAX_LEN) {
+ spice_warning("Too much SASL data %d", sasl->len);
+ return REDS_SASL_ERROR_INVALID_DATA;
+ }
+
+ if (sasl->len == 0) {
+ read_cb(opaque);
+ /* FIXME: can't report potential errors correctly here,
+ * but read_cb() will have done the needed RedLinkInfo cleanups
+ * if an error occurs, so the caller should not need to do more
+ * treatment */
+ return REDS_SASL_ERROR_OK;
+ } else {
+ sasl->data = spice_realloc(sasl->data, sasl->len);
+ obj->now = (uint8_t *)sasl->data;
+ obj->end = obj->now + sasl->len;
+ obj->done = read_cb;
+ async_read_handler(0, 0, obj);
+ return REDS_SASL_ERROR_OK;
+ }
+}
+
+/*
+ * Start Msg
+ *
+ * Input from client:
+ *
+ * u32 clientin-length
+ * u8-array clientin-string
+ *
+ * Output to client:
+ *
+ * u32 serverout-length
+ * u8-array serverout-strin
+ * u8 continue
+ */
+
+RedsSaslError reds_sasl_handle_auth_start(RedsStream *stream, AsyncReadDone read_cb, void *opaque)
+{
+ AsyncRead *obj = &stream->async_read;
+ const char *serverout;
+ unsigned int serveroutlen;
+ int err;
+ char *clientdata = NULL;
+ RedsSASL *sasl = &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() */
+ }
+
+ spice_info("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) {
+ spice_warning("sasl start failed %d (%s)",
+ err, sasl_errdetail(sasl->conn));
+ return REDS_SASL_ERROR_INVALID_DATA;
+ }
+
+ if (serveroutlen > SASL_DATA_MAX_LEN) {
+ spice_warning("sasl start reply data too long %d",
+ serveroutlen);
+ return REDS_SASL_ERROR_INVALID_DATA;
+ }
+
+ spice_info("SASL return data %d bytes, %p", serveroutlen, serverout);
+
+ if (serveroutlen) {
+ serveroutlen += 1;
+ reds_stream_write_all(stream, &serveroutlen, sizeof(uint32_t));
+ reds_stream_write_all(stream, serverout, serveroutlen);
+ } else {
+ reds_stream_write_all(stream, &serveroutlen, sizeof(uint32_t));
+ }
+
+ /* Whether auth is complete */
+ reds_stream_write_u8(stream, err == SASL_CONTINUE ? 0 : 1);
+
+ if (err == SASL_CONTINUE) {
+ spice_info("%s", "Authentication must continue (start)");
+ /* Wait for step length */
+ obj->now = (uint8_t *)&sasl->len;
+ obj->end = obj->now + sizeof(uint32_t);
+ obj->done = read_cb;
+ async_read_handler(0, 0, &stream->async_read);
+ return REDS_SASL_ERROR_CONTINUE;
+ } else {
+ int ssf;
+
+ if (auth_sasl_check_ssf(sasl, &ssf) == 0) {
+ spice_warning("Authentication rejected for weak SSF");
+ goto authreject;
+ }
+
+ spice_info("Authentication successful");
+ reds_stream_write_u32(stream, SPICE_LINK_ERR_OK); /* Accept auth */
+
+ /*
+ * Delay writing in SSF encoded until now
+ */
+ sasl->runSSF = ssf;
+ reds_stream_disable_writev(stream); /* make sure writev isn't called directly anymore */
+ return REDS_SASL_ERROR_OK;
+ }
+
+authreject:
+ reds_stream_write_u32(stream, 1); /* Reject auth */
+ reds_stream_write_u32(stream, sizeof("Authentication failed"));
+ reds_stream_write_all(stream, "Authentication failed", sizeof("Authentication failed"));
+
+ return REDS_SASL_ERROR_AUTH_FAILED;
+}
+
+RedsSaslError reds_sasl_handle_auth_startlen(RedsStream *stream, AsyncReadDone read_cb, void *opaque)
+{
+ AsyncRead *obj = &stream->async_read;
+ RedsSASL *sasl = &stream->sasl;
+
+ spice_info("Got client start len %d", sasl->len);
+ if (sasl->len > SASL_DATA_MAX_LEN) {
+ spice_warning("Too much SASL data %d", sasl->len);
+ return REDS_SASL_ERROR_INVALID_DATA;
+ }
+
+ if (sasl->len == 0) {
+ return REDS_SASL_ERROR_RETRY;
+ }
+
+ sasl->data = spice_realloc(sasl->data, sasl->len);
+ obj->now = (uint8_t *)sasl->data;
+ obj->end = obj->now + sasl->len;
+ obj->done = read_cb;
+ async_read_handler(0, 0, obj);
+
+ return REDS_SASL_ERROR_OK;
+}
+
+bool reds_sasl_handle_auth_mechname(RedsStream *stream, AsyncReadDone read_cb, void *opaque)
+{
+ AsyncRead *obj = &stream->async_read;
+ RedsSASL *sasl = &stream->sasl;
+
+ sasl->mechname[sasl->len] = '\0';
+ spice_info("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] != ',') {
+ spice_info("One %d", sasl->mechlist[sasl->len]);
+ return FALSE;
+ }
+ } else {
+ char *offset = strstr(sasl->mechlist, sasl->mechname);
+ spice_info("Two %p", offset);
+ if (!offset) {
+ return FALSE;
+ }
+ spice_info("Two '%s'", offset);
+ if (offset[-1] != ',' ||
+ (offset[sasl->len] != '\0'&&
+ offset[sasl->len] != ',')) {
+ return FALSE;
+ }
+ }
+
+ free(sasl->mechlist);
+ sasl->mechlist = spice_strdup(sasl->mechname);
+
+ spice_info("Validated mechname '%s'", sasl->mechname);
+
+ obj->now = (uint8_t *)&sasl->len;
+ obj->end = obj->now + sizeof(uint32_t);
+ obj->done = read_cb;
+ async_read_handler(0, 0, &stream->async_read);
+
+ return TRUE;
+}
+
+bool reds_sasl_handle_auth_mechlen(RedsStream *stream, AsyncReadDone read_cb, void *opaque)
+{
+ AsyncRead *obj = &stream->async_read;
+ RedsSASL *sasl = &stream->sasl;
+
+ if (sasl->len < 1 || sasl->len > 100) {
+ spice_warning("Got bad client mechname len %d", sasl->len);
+ return FALSE;
+ }
+
+ sasl->mechname = spice_malloc(sasl->len + 1);
+
+ spice_info("Wait for client mechname");
+ obj->now = (uint8_t *)sasl->mechname;
+ obj->end = obj->now + sasl->len;
+ obj->done = read_cb;
+ async_read_handler(0, 0, &stream->async_read);
+
+ return TRUE;
+}
+
+bool reds_sasl_start_auth(RedsStream *stream, AsyncReadDone read_cb, void *opaque)
+{
+ const char *mechlist = NULL;
+ sasl_security_properties_t secprops;
+ int err;
+ char *localAddr, *remoteAddr;
+ int mechlistlen;
+ AsyncRead *obj = &stream->async_read;
+ RedsSASL *sasl = &stream->sasl;
+
+ if (!(localAddr = reds_stream_get_local_address(stream))) {
+ goto error;
+ }
+
+ if (!(remoteAddr = reds_stream_get_remote_address(stream))) {
+ 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) {
+ spice_warning("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 (stream->ssl) {
+ sasl_ssf_t ssf;
+
+ ssf = SSL_get_cipher_bits(stream->ssl, NULL);
+ err = sasl_setprop(sasl->conn, SASL_SSF_EXTERNAL, &ssf);
+ if (err != SASL_OK) {
+ spice_warning("cannot set SASL external SSF %d (%s)",
+ err, sasl_errstring(err, NULL, NULL));
+ goto error_dispose;
+ }
+ } else {
+ sasl->wantSSF = 1;
+ }
+
+ memset(&secprops, 0, sizeof secprops);
+ /* Inform SASL that we've got an external SSF layer from TLS */
+ if (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) {
+ spice_warning("cannot set SASL security props %d (%s)",
+ err, sasl_errstring(err, NULL, NULL));
+ goto error_dispose;
+ }
+
+ err = sasl_listmech(sasl->conn,
+ NULL, /* Don't need to set user */
+ "", /* Prefix */
+ ",", /* Separator */
+ "", /* Suffix */
+ &mechlist,
+ NULL,
+ NULL);
+ if (err != SASL_OK || mechlist == NULL) {
+ spice_warning("cannot list SASL mechanisms %d (%s)",
+ err, sasl_errdetail(sasl->conn));
+ goto error_dispose;
+ }
+
+ spice_info("Available mechanisms for client: '%s'", mechlist);
+
+ sasl->mechlist = spice_strdup(mechlist);
+
+ mechlistlen = strlen(mechlist);
+ if (!reds_stream_write_all(stream, &mechlistlen, sizeof(uint32_t))
+ || !reds_stream_write_all(stream, sasl->mechlist, mechlistlen)) {
+ spice_warning("SASL mechanisms write error");
+ goto error;
+ }
+
+ spice_info("Wait for client mechname length");
+ obj->now = (uint8_t *)&sasl->len;
+ obj->end = obj->now + sizeof(uint32_t);
+ obj->done = read_cb;
+ obj->opaque = opaque;
+ async_read_handler(0, 0, obj);
+
+ return TRUE;
+
+error_dispose:
+ sasl_dispose(&sasl->conn);
+ sasl->conn = NULL;
+error:
+ return FALSE;
+}
#endif
diff --git a/server/reds_stream.h b/server/reds_stream.h
index fa41cbbd..cae244b2 100644
--- a/server/reds_stream.h
+++ b/server/reds_stream.h
@@ -115,6 +115,7 @@ ssize_t reds_stream_writev(RedsStream *s, const struct iovec *iov, int iovcnt);
bool reds_stream_write_all(RedsStream *stream, const void *in_buf, size_t n);
bool reds_stream_write_u8(RedsStream *s, uint8_t n);
bool reds_stream_write_u32(RedsStream *s, uint32_t n);
+void reds_stream_disable_writev(RedsStream *stream);
void reds_stream_free(RedsStream *s);
void reds_stream_push_channel_event(RedsStream *s, int event);
@@ -123,4 +124,21 @@ RedsStream *reds_stream_new(int socket);
RedsStreamSslStatus reds_stream_ssl_accept(RedsStream *stream);
int reds_stream_enable_ssl(RedsStream *stream, SSL_CTX *ctx);
+typedef enum {
+ REDS_SASL_ERROR_OK,
+ REDS_SASL_ERROR_GENERIC,
+ REDS_SASL_ERROR_INVALID_DATA,
+ REDS_SASL_ERROR_RETRY,
+ REDS_SASL_ERROR_CONTINUE,
+ REDS_SASL_ERROR_AUTH_FAILED
+} RedsSaslError;
+
+RedsSaslError reds_sasl_handle_auth_step(RedsStream *stream, AsyncReadDone read_cb, void *opaque);
+RedsSaslError reds_sasl_handle_auth_steplen(RedsStream *stream, AsyncReadDone read_cb, void *opaque);
+RedsSaslError reds_sasl_handle_auth_start(RedsStream *stream, AsyncReadDone read_cb, void *opaque);
+RedsSaslError reds_sasl_handle_auth_startlen(RedsStream *stream, AsyncReadDone read_cb, void *opaque);
+bool reds_sasl_handle_auth_mechname(RedsStream *stream, AsyncReadDone read_cb, void *opaque);
+bool reds_sasl_handle_auth_mechlen(RedsStream *stream, AsyncReadDone read_cb, void *opaque);
+bool reds_sasl_start_auth(RedsStream *stream, AsyncReadDone read_cb, void *opaque);
+
#endif