From a32d96e695289c3d87c847a13a3d2d919e7b9d85 Mon Sep 17 00:00:00 2001 From: Jonathon Jongsma Date: Wed, 14 Jan 2015 11:12:28 -0600 Subject: Rename reds_stream.[ch] to red-stream.[ch] --- server/Makefile.am | 4 +- server/display-channel.h | 2 +- server/inputs-channel.c | 2 +- server/red-channel.c | 2 +- server/red-channel.h | 2 +- server/reds-stream.c | 1060 ++++++++++++++++++++++++++++++++++++++++++++++ server/reds-stream.h | 93 ++++ server/reds.c | 2 +- server/reds_stream.c | 1060 ---------------------------------------------- server/reds_stream.h | 93 ---- 10 files changed, 1160 insertions(+), 1160 deletions(-) create mode 100644 server/reds-stream.c create mode 100644 server/reds-stream.h delete mode 100644 server/reds_stream.c delete mode 100644 server/reds_stream.h diff --git a/server/Makefile.am b/server/Makefile.am index 50d30be6..e2633e1f 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -110,8 +110,8 @@ libspice_server_la_SOURCES = \ reds.c \ reds.h \ reds-private.h \ - reds_stream.c \ - reds_stream.h \ + reds-stream.c \ + reds-stream.h \ sound.c \ sound.h \ spice-experimental.h \ diff --git a/server/display-channel.h b/server/display-channel.h index da99b9c5..7f054c28 100644 --- a/server/display-channel.h +++ b/server/display-channel.h @@ -5,7 +5,7 @@ #include "common/rect.h" #include "red-worker.h" -#include "reds_stream.h" +#include "reds-stream.h" #include "cache-item.h" #include "pixmap-cache.h" #include "canvas.h" diff --git a/server/inputs-channel.c b/server/inputs-channel.c index 692c244a..d12d8628 100644 --- a/server/inputs-channel.c +++ b/server/inputs-channel.c @@ -37,7 +37,7 @@ #include "spice.h" #include "common.h" #include "reds.h" -#include "reds_stream.h" +#include "reds-stream.h" #include "red-channel.h" #include "main-channel.h" #include "inputs-channel.h" diff --git a/server/red-channel.c b/server/red-channel.c index dac3ca92..172d2cae 100644 --- a/server/red-channel.c +++ b/server/red-channel.c @@ -40,7 +40,7 @@ #include "stat.h" #include "red-channel.h" #include "reds.h" -#include "reds_stream.h" +#include "reds-stream.h" #include "main-dispatcher.h" #include "utils.h" diff --git a/server/red-channel.h b/server/red-channel.h index b023667d..1c08724a 100644 --- a/server/red-channel.h +++ b/server/red-channel.h @@ -31,7 +31,7 @@ #include "spice.h" #include "common.h" #include "demarshallers.h" -#include "reds_stream.h" +#include "reds-stream.h" #include "stat.h" #define MAX_SEND_BUFS 1000 diff --git a/server/reds-stream.c b/server/reds-stream.c new file mode 100644 index 00000000..a8991435 --- /dev/null +++ b/server/reds-stream.c @@ -0,0 +1,1060 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009, 2013 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "common.h" +#include "main-dispatcher.h" +#include "reds-stream.h" +#include "common/log.h" + +#include +#include +#include +#include + +#include + +#include + +struct AsyncRead { + RedsStream *stream; + void *opaque; + uint8_t *now; + uint8_t *end; + AsyncReadDone done; + AsyncReadError error; +}; +typedef struct AsyncRead AsyncRead; + +extern SpiceCoreInterface *core; + +#if HAVE_SASL +#include + +typedef struct RedsSASL { + 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; +} RedsSASL; +#endif + +struct RedsStreamPrivate { + SSL *ssl; + +#if HAVE_SASL + RedsSASL sasl; +#endif + + AsyncRead async_read; + + /* life time of info: + * allocated when creating RedsStream. + * deallocated when main_dispatcher handles the SPICE_CHANNEL_EVENT_DISCONNECTED + * event, either from same thread or by call back from main thread. */ + SpiceChannelEventInfo* info; + + ssize_t (*read)(RedsStream *s, void *buf, size_t nbyte); + ssize_t (*write)(RedsStream *s, const void *buf, size_t nbyte); + ssize_t (*writev)(RedsStream *s, const struct iovec *iov, int iovcnt); + +}; + +static ssize_t stream_write_cb(RedsStream *s, const void *buf, size_t size) +{ + return write(s->socket, buf, size); +} + +static ssize_t stream_writev_cb(RedsStream *s, const struct iovec *iov, int iovcnt) +{ + ssize_t ret = 0; + do { + int tosend; + ssize_t n, expected = 0; + int i; +#ifdef IOV_MAX + tosend = MIN(iovcnt, IOV_MAX); +#else + tosend = iovcnt; +#endif + for (i = 0; i < tosend; i++) { + expected += iov[i].iov_len; + } + n = writev(s->socket, iov, tosend); + if (n <= expected) { + if (n > 0) + ret += n; + return ret == 0 ? n : ret; + } + ret += n; + iov += tosend; + iovcnt -= tosend; + } while(iovcnt > 0); + + return ret; +} + +static ssize_t stream_read_cb(RedsStream *s, void *buf, size_t size) +{ + return read(s->socket, buf, size); +} + +static ssize_t stream_ssl_write_cb(RedsStream *s, const void *buf, size_t size) +{ + int return_code; + SPICE_GNUC_UNUSED int ssl_error; + + return_code = SSL_write(s->priv->ssl, buf, size); + + if (return_code < 0) { + ssl_error = SSL_get_error(s->priv->ssl, return_code); + } + + return return_code; +} + +static ssize_t stream_ssl_read_cb(RedsStream *s, void *buf, size_t size) +{ + int return_code; + SPICE_GNUC_UNUSED int ssl_error; + + return_code = SSL_read(s->priv->ssl, buf, size); + + if (return_code < 0) { + ssl_error = SSL_get_error(s->priv->ssl, return_code); + } + + return return_code; +} + +void reds_stream_remove_watch(RedsStream* s) +{ + if (s->watch) { + core->watch_remove(s->watch); + s->watch = NULL; + } +} + +#if HAVE_SASL +static ssize_t reds_stream_sasl_read(RedsStream *s, uint8_t *buf, size_t nbyte); +#endif + +ssize_t reds_stream_read(RedsStream *s, void *buf, size_t nbyte) +{ + ssize_t ret; + +#if HAVE_SASL + if (s->priv->sasl.conn && s->priv->sasl.runSSF) { + ret = reds_stream_sasl_read(s, buf, nbyte); + } else +#endif + ret = s->priv->read(s, buf, nbyte); + + return ret; +} + +bool reds_stream_write_all(RedsStream *stream, const void *in_buf, size_t n) +{ + const uint8_t *buf = (uint8_t *)in_buf; + + while (n) { + int now = reds_stream_write(stream, buf, n); + if (now <= 0) { + if (now == -1 && (errno == EINTR || errno == EAGAIN)) { + continue; + } + return FALSE; + } + n -= now; + buf += now; + } + return TRUE; +} + +#if HAVE_SASL +static ssize_t reds_stream_sasl_write(RedsStream *s, const void *buf, size_t nbyte); +#endif + +ssize_t reds_stream_write(RedsStream *s, const void *buf, size_t nbyte) +{ + ssize_t ret; + +#if HAVE_SASL + if (s->priv->sasl.conn && s->priv->sasl.runSSF) { + ret = reds_stream_sasl_write(s, buf, nbyte); + } else +#endif + ret = s->priv->write(s, buf, nbyte); + + return ret; +} + +ssize_t reds_stream_writev(RedsStream *s, const struct iovec *iov, int iovcnt) +{ + int i; + int n; + ssize_t ret = 0; + + if (s->priv->writev != NULL) { + return s->priv->writev(s, iov, iovcnt); + } + + for (i = 0; i < iovcnt; ++i) { + n = reds_stream_write(s, iov[i].iov_base, iov[i].iov_len); + if (n <= 0) + return ret == 0 ? n : ret; + ret += n; + } + + return ret; +} + +void reds_stream_free(RedsStream *s) +{ + if (!s) { + return; + } + + reds_stream_push_channel_event(s, SPICE_CHANNEL_EVENT_DISCONNECTED); + +#if HAVE_SASL + if (s->priv->sasl.conn) { + s->priv->sasl.runSSF = s->priv->sasl.wantSSF = 0; + s->priv->sasl.len = 0; + s->priv->sasl.encodedLength = s->priv->sasl.encodedOffset = 0; + s->priv->sasl.encoded = NULL; + free(s->priv->sasl.mechlist); + free(s->priv->sasl.mechname); + s->priv->sasl.mechlist = NULL; + sasl_dispose(&s->priv->sasl.conn); + s->priv->sasl.conn = NULL; + } +#endif + + if (s->priv->ssl) { + SSL_free(s->priv->ssl); + } + + reds_stream_remove_watch(s); + spice_info("close socket fd %d", s->socket); + close(s->socket); + + free(s); +} + +void reds_stream_push_channel_event(RedsStream *s, int event) +{ + main_dispatcher_channel_event(event, s->priv->info); +} + +static void reds_stream_set_socket(RedsStream *stream, int socket) +{ + stream->socket = socket; + /* deprecated fields. Filling them for backward compatibility */ + stream->priv->info->llen = sizeof(stream->priv->info->laddr); + stream->priv->info->plen = sizeof(stream->priv->info->paddr); + getsockname(stream->socket, (struct sockaddr*)(&stream->priv->info->laddr), &stream->priv->info->llen); + getpeername(stream->socket, (struct sockaddr*)(&stream->priv->info->paddr), &stream->priv->info->plen); + + stream->priv->info->flags |= SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT; + stream->priv->info->llen_ext = sizeof(stream->priv->info->laddr_ext); + stream->priv->info->plen_ext = sizeof(stream->priv->info->paddr_ext); + getsockname(stream->socket, (struct sockaddr*)(&stream->priv->info->laddr_ext), + &stream->priv->info->llen_ext); + getpeername(stream->socket, (struct sockaddr*)(&stream->priv->info->paddr_ext), + &stream->priv->info->plen_ext); +} + + +void reds_stream_set_channel(RedsStream *stream, int connection_id, + int channel_type, int channel_id) +{ + stream->priv->info->connection_id = connection_id; + stream->priv->info->type = channel_type; + stream->priv->info->id = channel_id; +} + +RedsStream *reds_stream_new(int socket) +{ + RedsStream *stream; + + stream = spice_malloc0(sizeof(RedsStream) + sizeof(RedsStreamPrivate)); + stream->priv = (RedsStreamPrivate *)(((char *)stream) + sizeof(RedsStream)); + stream->priv->info = spice_new0(SpiceChannelEventInfo, 1); + reds_stream_set_socket(stream, socket); + + stream->priv->read = stream_read_cb; + stream->priv->write = stream_write_cb; + stream->priv->writev = stream_writev_cb; + + return stream; +} + +bool reds_stream_is_ssl(RedsStream *stream) +{ + return (stream->priv->ssl != NULL); +} + +void reds_stream_set_info_flag(RedsStream *stream, unsigned int flag) +{ + g_return_if_fail((flag == SPICE_CHANNEL_EVENT_FLAG_TLS) + || (flag == SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT)); + + stream->priv->info->flags |= flag; +} + +void reds_stream_disable_writev(RedsStream *stream) +{ + stream->priv->writev = NULL; +} + +RedsStreamSslStatus reds_stream_ssl_accept(RedsStream *stream) +{ + int ssl_error; + int return_code; + + return_code = SSL_accept(stream->priv->ssl); + if (return_code == 1) { + return REDS_STREAM_SSL_STATUS_OK; + } + + ssl_error = SSL_get_error(stream->priv->ssl, return_code); + if (return_code == -1 && (ssl_error == SSL_ERROR_WANT_READ || + ssl_error == SSL_ERROR_WANT_WRITE)) { + if (ssl_error == SSL_ERROR_WANT_READ) { + return REDS_STREAM_SSL_STATUS_WAIT_FOR_READ; + } else { + return REDS_STREAM_SSL_STATUS_WAIT_FOR_WRITE; + } + } + + ERR_print_errors_fp(stderr); + spice_warning("SSL_accept failed, error=%d", ssl_error); + SSL_free(stream->priv->ssl); + stream->priv->ssl = NULL; + + return REDS_STREAM_SSL_STATUS_ERROR; +} + +int reds_stream_enable_ssl(RedsStream *stream, SSL_CTX *ctx) +{ + BIO *sbio; + + // Handle SSL handshaking + if (!(sbio = BIO_new_socket(stream->socket, BIO_NOCLOSE))) { + spice_warning("could not allocate ssl bio socket"); + return REDS_STREAM_SSL_STATUS_ERROR; + } + + stream->priv->ssl = SSL_new(ctx); + if (!stream->priv->ssl) { + spice_warning("could not allocate ssl context"); + BIO_free(sbio); + return REDS_STREAM_SSL_STATUS_ERROR; + } + + SSL_set_bio(stream->priv->ssl, sbio, sbio); + + stream->priv->write = stream_ssl_write_cb; + stream->priv->read = stream_ssl_read_cb; + stream->priv->writev = NULL; + + return reds_stream_ssl_accept(stream); +} + +void reds_stream_set_async_error_handler(RedsStream *stream, + AsyncReadError error_handler) +{ + stream->priv->async_read.error = error_handler; +} + +static inline void async_read_clear_handlers(AsyncRead *async) +{ + if (async->stream->watch) { + reds_stream_remove_watch(async->stream); + } + async->stream = NULL; +} + +static void async_read_handler(G_GNUC_UNUSED int fd, + G_GNUC_UNUSED int event, + void *data) +{ + AsyncRead *async = (AsyncRead *)data; + + for (;;) { + int n = async->end - async->now; + + spice_assert(n > 0); + n = reds_stream_read(async->stream, async->now, n); + if (n <= 0) { + if (n < 0) { + switch (errno) { + case EAGAIN: + if (!async->stream->watch) { + async->stream->watch = core->watch_add(async->stream->socket, + SPICE_WATCH_EVENT_READ, + async_read_handler, async); + } + return; + case EINTR: + break; + default: + async_read_clear_handlers(async); + if (async->error) { + async->error(async->opaque, errno); + } + return; + } + } else { + async_read_clear_handlers(async); + if (async->error) { + async->error(async->opaque, 0); + } + return; + } + } else { + async->now += n; + if (async->now == async->end) { + async_read_clear_handlers(async); + async->done(async->opaque); + return; + } + } + } +} + +void reds_stream_async_read(RedsStream *stream, + uint8_t *data, size_t size, + AsyncReadDone read_done_cb, + void *opaque) +{ + AsyncRead *async = &stream->priv->async_read; + + g_return_if_fail(!async->stream); + async->stream = stream; + async->now = data; + async->end = async->now + size; + async->done = read_done_cb; + async->opaque = opaque; + async_read_handler(0, 0, async); + +} + +#if HAVE_SASL +bool reds_stream_write_u8(RedsStream *s, uint8_t n) +{ + return reds_stream_write_all(s, &n, sizeof(uint8_t)); +} + +bool reds_stream_write_u32(RedsStream *s, uint32_t n) +{ + return reds_stream_write_all(s, &n, sizeof(uint32_t)); +} + +static ssize_t reds_stream_sasl_write(RedsStream *s, const void *buf, size_t nbyte) +{ + ssize_t ret; + + if (!s->priv->sasl.encoded) { + int err; + err = sasl_encode(s->priv->sasl.conn, (char *)buf, nbyte, + (const char **)&s->priv->sasl.encoded, + &s->priv->sasl.encodedLength); + if (err != SASL_OK) { + spice_warning("sasl_encode error: %d", err); + return -1; + } + + if (s->priv->sasl.encodedLength == 0) { + return 0; + } + + if (!s->priv->sasl.encoded) { + spice_warning("sasl_encode didn't return a buffer!"); + return 0; + } + + s->priv->sasl.encodedOffset = 0; + } + + ret = s->priv->write(s, s->priv->sasl.encoded + s->priv->sasl.encodedOffset, + s->priv->sasl.encodedLength - s->priv->sasl.encodedOffset); + + if (ret <= 0) { + return ret; + } + + s->priv->sasl.encodedOffset += ret; + if (s->priv->sasl.encodedOffset == s->priv->sasl.encodedLength) { + s->priv->sasl.encoded = NULL; + s->priv->sasl.encodedOffset = s->priv->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, uint8_t *buf, size_t nbyte) +{ + uint8_t encoded[4096]; + const char *decoded; + unsigned int decodedlen; + int err; + int n; + + n = spice_buffer_copy(&s->priv->sasl.inbuffer, buf, nbyte); + if (n > 0) { + spice_buffer_remove(&s->priv->sasl.inbuffer, n); + if (n == nbyte) + return n; + nbyte -= n; + buf += n; + } + + n = s->priv->read(s, encoded, sizeof(encoded)); + if (n <= 0) { + return n; + } + + err = sasl_decode(s->priv->sasl.conn, + (char *)encoded, n, + &decoded, &decodedlen); + if (err != SASL_OK) { + spice_warning("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->priv->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->priv->info->laddr_ext, + stream->priv->info->llen_ext); +} + +static char *reds_stream_get_remote_address(RedsStream *stream) +{ + return addr_to_string("%s;%s", &stream->priv->info->paddr_ext, + stream->priv->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->priv->sasl; + uint32_t datalen = sasl->len; + + /* 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 */ + reds_stream_async_read(stream, (uint8_t *)&sasl->len, sizeof(uint32_t), + read_cb, opaque); + 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) +{ + RedsSASL *sasl = &stream->priv->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); + reds_stream_async_read(stream, (uint8_t *)sasl->data, sasl->len, + read_cb, opaque); + 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) +{ + const char *serverout; + unsigned int serveroutlen; + int err; + char *clientdata = NULL; + RedsSASL *sasl = &stream->priv->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 */ + reds_stream_async_read(stream, (uint8_t *)&sasl->len, sizeof(uint32_t), + read_cb, opaque); + 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) +{ + RedsSASL *sasl = &stream->priv->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); + reds_stream_async_read(stream, (uint8_t *)sasl->data, sasl->len, + read_cb, opaque); + + return REDS_SASL_ERROR_OK; +} + +bool reds_sasl_handle_auth_mechname(RedsStream *stream, AsyncReadDone read_cb, void *opaque) +{ + RedsSASL *sasl = &stream->priv->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); + + reds_stream_async_read(stream, (uint8_t *)&sasl->len, sizeof(uint32_t), + read_cb, opaque); + + return TRUE; +} + +bool reds_sasl_handle_auth_mechlen(RedsStream *stream, AsyncReadDone read_cb, void *opaque) +{ + RedsSASL *sasl = &stream->priv->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"); + reds_stream_async_read(stream, (uint8_t *)sasl->mechname, sasl->len, + read_cb, opaque); + + 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; + RedsSASL *sasl = &stream->priv->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->priv->ssl) { + sasl_ssf_t ssf; + + ssf = SSL_get_cipher_bits(stream->priv->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->priv->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"); + reds_stream_async_read(stream, (uint8_t *)&sasl->len, sizeof(uint32_t), + read_cb, opaque); + + 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 new file mode 100644 index 00000000..6cbbbbbe --- /dev/null +++ b/server/reds-stream.h @@ -0,0 +1,93 @@ +/* + Copyright (C) 2009, 2013 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ + +#ifndef _H_REDS_STREAM +#define _H_REDS_STREAM + +#include "spice.h" +#include "common/mem.h" + +#include + +#include + +typedef void (*AsyncReadDone)(void *opaque); +typedef void (*AsyncReadError)(void *opaque, int err); + +typedef struct RedsStream RedsStream; +typedef struct RedsStreamPrivate RedsStreamPrivate; + +struct RedsStream { + int socket; + SpiceWatch *watch; + + /* set it to TRUE if you shutdown the socket. shutdown read doesn't work as accepted - + receive may return data afterward. check the flag before calling receive*/ + int shutdown; + + RedsStreamPrivate *priv; +}; + +typedef enum { + REDS_STREAM_SSL_STATUS_OK, + REDS_STREAM_SSL_STATUS_ERROR, + REDS_STREAM_SSL_STATUS_WAIT_FOR_READ, + REDS_STREAM_SSL_STATUS_WAIT_FOR_WRITE +} RedsStreamSslStatus; + +/* any thread */ +ssize_t reds_stream_read(RedsStream *s, void *buf, size_t nbyte); +void reds_stream_async_read(RedsStream *stream, uint8_t *data, size_t size, + AsyncReadDone read_done_cb, void *opaque); +void reds_stream_set_async_error_handler(RedsStream *stream, + AsyncReadError error_handler); +ssize_t reds_stream_write(RedsStream *s, const void *buf, size_t nbyte); +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); +void reds_stream_remove_watch(RedsStream* s); +void reds_stream_set_channel(RedsStream *stream, int connection_id, + int channel_type, int channel_id); +RedsStream *reds_stream_new(int socket); +bool reds_stream_is_ssl(RedsStream *stream); +RedsStreamSslStatus reds_stream_ssl_accept(RedsStream *stream); +int reds_stream_enable_ssl(RedsStream *stream, SSL_CTX *ctx); +void reds_stream_set_info_flag(RedsStream *stream, unsigned int flag); + +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 diff --git a/server/reds.c b/server/reds.c index 9b4e5b52..0c40980e 100644 --- a/server/reds.c +++ b/server/reds.c @@ -69,7 +69,7 @@ #ifdef USE_SMARTCARD #include "smartcard.h" #endif -#include "reds_stream.h" +#include "reds-stream.h" #include "reds-private.h" diff --git a/server/reds_stream.c b/server/reds_stream.c deleted file mode 100644 index 68c37740..00000000 --- a/server/reds_stream.c +++ /dev/null @@ -1,1060 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009, 2013 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "common.h" -#include "main-dispatcher.h" -#include "reds_stream.h" -#include "common/log.h" - -#include -#include -#include -#include - -#include - -#include - -struct AsyncRead { - RedsStream *stream; - void *opaque; - uint8_t *now; - uint8_t *end; - AsyncReadDone done; - AsyncReadError error; -}; -typedef struct AsyncRead AsyncRead; - -extern SpiceCoreInterface *core; - -#if HAVE_SASL -#include - -typedef struct RedsSASL { - 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; -} RedsSASL; -#endif - -struct RedsStreamPrivate { - SSL *ssl; - -#if HAVE_SASL - RedsSASL sasl; -#endif - - AsyncRead async_read; - - /* life time of info: - * allocated when creating RedsStream. - * deallocated when main_dispatcher handles the SPICE_CHANNEL_EVENT_DISCONNECTED - * event, either from same thread or by call back from main thread. */ - SpiceChannelEventInfo* info; - - ssize_t (*read)(RedsStream *s, void *buf, size_t nbyte); - ssize_t (*write)(RedsStream *s, const void *buf, size_t nbyte); - ssize_t (*writev)(RedsStream *s, const struct iovec *iov, int iovcnt); - -}; - -static ssize_t stream_write_cb(RedsStream *s, const void *buf, size_t size) -{ - return write(s->socket, buf, size); -} - -static ssize_t stream_writev_cb(RedsStream *s, const struct iovec *iov, int iovcnt) -{ - ssize_t ret = 0; - do { - int tosend; - ssize_t n, expected = 0; - int i; -#ifdef IOV_MAX - tosend = MIN(iovcnt, IOV_MAX); -#else - tosend = iovcnt; -#endif - for (i = 0; i < tosend; i++) { - expected += iov[i].iov_len; - } - n = writev(s->socket, iov, tosend); - if (n <= expected) { - if (n > 0) - ret += n; - return ret == 0 ? n : ret; - } - ret += n; - iov += tosend; - iovcnt -= tosend; - } while(iovcnt > 0); - - return ret; -} - -static ssize_t stream_read_cb(RedsStream *s, void *buf, size_t size) -{ - return read(s->socket, buf, size); -} - -static ssize_t stream_ssl_write_cb(RedsStream *s, const void *buf, size_t size) -{ - int return_code; - SPICE_GNUC_UNUSED int ssl_error; - - return_code = SSL_write(s->priv->ssl, buf, size); - - if (return_code < 0) { - ssl_error = SSL_get_error(s->priv->ssl, return_code); - } - - return return_code; -} - -static ssize_t stream_ssl_read_cb(RedsStream *s, void *buf, size_t size) -{ - int return_code; - SPICE_GNUC_UNUSED int ssl_error; - - return_code = SSL_read(s->priv->ssl, buf, size); - - if (return_code < 0) { - ssl_error = SSL_get_error(s->priv->ssl, return_code); - } - - return return_code; -} - -void reds_stream_remove_watch(RedsStream* s) -{ - if (s->watch) { - core->watch_remove(s->watch); - s->watch = NULL; - } -} - -#if HAVE_SASL -static ssize_t reds_stream_sasl_read(RedsStream *s, uint8_t *buf, size_t nbyte); -#endif - -ssize_t reds_stream_read(RedsStream *s, void *buf, size_t nbyte) -{ - ssize_t ret; - -#if HAVE_SASL - if (s->priv->sasl.conn && s->priv->sasl.runSSF) { - ret = reds_stream_sasl_read(s, buf, nbyte); - } else -#endif - ret = s->priv->read(s, buf, nbyte); - - return ret; -} - -bool reds_stream_write_all(RedsStream *stream, const void *in_buf, size_t n) -{ - const uint8_t *buf = (uint8_t *)in_buf; - - while (n) { - int now = reds_stream_write(stream, buf, n); - if (now <= 0) { - if (now == -1 && (errno == EINTR || errno == EAGAIN)) { - continue; - } - return FALSE; - } - n -= now; - buf += now; - } - return TRUE; -} - -#if HAVE_SASL -static ssize_t reds_stream_sasl_write(RedsStream *s, const void *buf, size_t nbyte); -#endif - -ssize_t reds_stream_write(RedsStream *s, const void *buf, size_t nbyte) -{ - ssize_t ret; - -#if HAVE_SASL - if (s->priv->sasl.conn && s->priv->sasl.runSSF) { - ret = reds_stream_sasl_write(s, buf, nbyte); - } else -#endif - ret = s->priv->write(s, buf, nbyte); - - return ret; -} - -ssize_t reds_stream_writev(RedsStream *s, const struct iovec *iov, int iovcnt) -{ - int i; - int n; - ssize_t ret = 0; - - if (s->priv->writev != NULL) { - return s->priv->writev(s, iov, iovcnt); - } - - for (i = 0; i < iovcnt; ++i) { - n = reds_stream_write(s, iov[i].iov_base, iov[i].iov_len); - if (n <= 0) - return ret == 0 ? n : ret; - ret += n; - } - - return ret; -} - -void reds_stream_free(RedsStream *s) -{ - if (!s) { - return; - } - - reds_stream_push_channel_event(s, SPICE_CHANNEL_EVENT_DISCONNECTED); - -#if HAVE_SASL - if (s->priv->sasl.conn) { - s->priv->sasl.runSSF = s->priv->sasl.wantSSF = 0; - s->priv->sasl.len = 0; - s->priv->sasl.encodedLength = s->priv->sasl.encodedOffset = 0; - s->priv->sasl.encoded = NULL; - free(s->priv->sasl.mechlist); - free(s->priv->sasl.mechname); - s->priv->sasl.mechlist = NULL; - sasl_dispose(&s->priv->sasl.conn); - s->priv->sasl.conn = NULL; - } -#endif - - if (s->priv->ssl) { - SSL_free(s->priv->ssl); - } - - reds_stream_remove_watch(s); - spice_info("close socket fd %d", s->socket); - close(s->socket); - - free(s); -} - -void reds_stream_push_channel_event(RedsStream *s, int event) -{ - main_dispatcher_channel_event(event, s->priv->info); -} - -static void reds_stream_set_socket(RedsStream *stream, int socket) -{ - stream->socket = socket; - /* deprecated fields. Filling them for backward compatibility */ - stream->priv->info->llen = sizeof(stream->priv->info->laddr); - stream->priv->info->plen = sizeof(stream->priv->info->paddr); - getsockname(stream->socket, (struct sockaddr*)(&stream->priv->info->laddr), &stream->priv->info->llen); - getpeername(stream->socket, (struct sockaddr*)(&stream->priv->info->paddr), &stream->priv->info->plen); - - stream->priv->info->flags |= SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT; - stream->priv->info->llen_ext = sizeof(stream->priv->info->laddr_ext); - stream->priv->info->plen_ext = sizeof(stream->priv->info->paddr_ext); - getsockname(stream->socket, (struct sockaddr*)(&stream->priv->info->laddr_ext), - &stream->priv->info->llen_ext); - getpeername(stream->socket, (struct sockaddr*)(&stream->priv->info->paddr_ext), - &stream->priv->info->plen_ext); -} - - -void reds_stream_set_channel(RedsStream *stream, int connection_id, - int channel_type, int channel_id) -{ - stream->priv->info->connection_id = connection_id; - stream->priv->info->type = channel_type; - stream->priv->info->id = channel_id; -} - -RedsStream *reds_stream_new(int socket) -{ - RedsStream *stream; - - stream = spice_malloc0(sizeof(RedsStream) + sizeof(RedsStreamPrivate)); - stream->priv = (RedsStreamPrivate *)(((char *)stream) + sizeof(RedsStream)); - stream->priv->info = spice_new0(SpiceChannelEventInfo, 1); - reds_stream_set_socket(stream, socket); - - stream->priv->read = stream_read_cb; - stream->priv->write = stream_write_cb; - stream->priv->writev = stream_writev_cb; - - return stream; -} - -bool reds_stream_is_ssl(RedsStream *stream) -{ - return (stream->priv->ssl != NULL); -} - -void reds_stream_set_info_flag(RedsStream *stream, unsigned int flag) -{ - g_return_if_fail((flag == SPICE_CHANNEL_EVENT_FLAG_TLS) - || (flag == SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT)); - - stream->priv->info->flags |= flag; -} - -void reds_stream_disable_writev(RedsStream *stream) -{ - stream->priv->writev = NULL; -} - -RedsStreamSslStatus reds_stream_ssl_accept(RedsStream *stream) -{ - int ssl_error; - int return_code; - - return_code = SSL_accept(stream->priv->ssl); - if (return_code == 1) { - return REDS_STREAM_SSL_STATUS_OK; - } - - ssl_error = SSL_get_error(stream->priv->ssl, return_code); - if (return_code == -1 && (ssl_error == SSL_ERROR_WANT_READ || - ssl_error == SSL_ERROR_WANT_WRITE)) { - if (ssl_error == SSL_ERROR_WANT_READ) { - return REDS_STREAM_SSL_STATUS_WAIT_FOR_READ; - } else { - return REDS_STREAM_SSL_STATUS_WAIT_FOR_WRITE; - } - } - - ERR_print_errors_fp(stderr); - spice_warning("SSL_accept failed, error=%d", ssl_error); - SSL_free(stream->priv->ssl); - stream->priv->ssl = NULL; - - return REDS_STREAM_SSL_STATUS_ERROR; -} - -int reds_stream_enable_ssl(RedsStream *stream, SSL_CTX *ctx) -{ - BIO *sbio; - - // Handle SSL handshaking - if (!(sbio = BIO_new_socket(stream->socket, BIO_NOCLOSE))) { - spice_warning("could not allocate ssl bio socket"); - return REDS_STREAM_SSL_STATUS_ERROR; - } - - stream->priv->ssl = SSL_new(ctx); - if (!stream->priv->ssl) { - spice_warning("could not allocate ssl context"); - BIO_free(sbio); - return REDS_STREAM_SSL_STATUS_ERROR; - } - - SSL_set_bio(stream->priv->ssl, sbio, sbio); - - stream->priv->write = stream_ssl_write_cb; - stream->priv->read = stream_ssl_read_cb; - stream->priv->writev = NULL; - - return reds_stream_ssl_accept(stream); -} - -void reds_stream_set_async_error_handler(RedsStream *stream, - AsyncReadError error_handler) -{ - stream->priv->async_read.error = error_handler; -} - -static inline void async_read_clear_handlers(AsyncRead *async) -{ - if (async->stream->watch) { - reds_stream_remove_watch(async->stream); - } - async->stream = NULL; -} - -static void async_read_handler(G_GNUC_UNUSED int fd, - G_GNUC_UNUSED int event, - void *data) -{ - AsyncRead *async = (AsyncRead *)data; - - for (;;) { - int n = async->end - async->now; - - spice_assert(n > 0); - n = reds_stream_read(async->stream, async->now, n); - if (n <= 0) { - if (n < 0) { - switch (errno) { - case EAGAIN: - if (!async->stream->watch) { - async->stream->watch = core->watch_add(async->stream->socket, - SPICE_WATCH_EVENT_READ, - async_read_handler, async); - } - return; - case EINTR: - break; - default: - async_read_clear_handlers(async); - if (async->error) { - async->error(async->opaque, errno); - } - return; - } - } else { - async_read_clear_handlers(async); - if (async->error) { - async->error(async->opaque, 0); - } - return; - } - } else { - async->now += n; - if (async->now == async->end) { - async_read_clear_handlers(async); - async->done(async->opaque); - return; - } - } - } -} - -void reds_stream_async_read(RedsStream *stream, - uint8_t *data, size_t size, - AsyncReadDone read_done_cb, - void *opaque) -{ - AsyncRead *async = &stream->priv->async_read; - - g_return_if_fail(!async->stream); - async->stream = stream; - async->now = data; - async->end = async->now + size; - async->done = read_done_cb; - async->opaque = opaque; - async_read_handler(0, 0, async); - -} - -#if HAVE_SASL -bool reds_stream_write_u8(RedsStream *s, uint8_t n) -{ - return reds_stream_write_all(s, &n, sizeof(uint8_t)); -} - -bool reds_stream_write_u32(RedsStream *s, uint32_t n) -{ - return reds_stream_write_all(s, &n, sizeof(uint32_t)); -} - -static ssize_t reds_stream_sasl_write(RedsStream *s, const void *buf, size_t nbyte) -{ - ssize_t ret; - - if (!s->priv->sasl.encoded) { - int err; - err = sasl_encode(s->priv->sasl.conn, (char *)buf, nbyte, - (const char **)&s->priv->sasl.encoded, - &s->priv->sasl.encodedLength); - if (err != SASL_OK) { - spice_warning("sasl_encode error: %d", err); - return -1; - } - - if (s->priv->sasl.encodedLength == 0) { - return 0; - } - - if (!s->priv->sasl.encoded) { - spice_warning("sasl_encode didn't return a buffer!"); - return 0; - } - - s->priv->sasl.encodedOffset = 0; - } - - ret = s->priv->write(s, s->priv->sasl.encoded + s->priv->sasl.encodedOffset, - s->priv->sasl.encodedLength - s->priv->sasl.encodedOffset); - - if (ret <= 0) { - return ret; - } - - s->priv->sasl.encodedOffset += ret; - if (s->priv->sasl.encodedOffset == s->priv->sasl.encodedLength) { - s->priv->sasl.encoded = NULL; - s->priv->sasl.encodedOffset = s->priv->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, uint8_t *buf, size_t nbyte) -{ - uint8_t encoded[4096]; - const char *decoded; - unsigned int decodedlen; - int err; - int n; - - n = spice_buffer_copy(&s->priv->sasl.inbuffer, buf, nbyte); - if (n > 0) { - spice_buffer_remove(&s->priv->sasl.inbuffer, n); - if (n == nbyte) - return n; - nbyte -= n; - buf += n; - } - - n = s->priv->read(s, encoded, sizeof(encoded)); - if (n <= 0) { - return n; - } - - err = sasl_decode(s->priv->sasl.conn, - (char *)encoded, n, - &decoded, &decodedlen); - if (err != SASL_OK) { - spice_warning("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->priv->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->priv->info->laddr_ext, - stream->priv->info->llen_ext); -} - -static char *reds_stream_get_remote_address(RedsStream *stream) -{ - return addr_to_string("%s;%s", &stream->priv->info->paddr_ext, - stream->priv->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->priv->sasl; - uint32_t datalen = sasl->len; - - /* 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 */ - reds_stream_async_read(stream, (uint8_t *)&sasl->len, sizeof(uint32_t), - read_cb, opaque); - 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) -{ - RedsSASL *sasl = &stream->priv->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); - reds_stream_async_read(stream, (uint8_t *)sasl->data, sasl->len, - read_cb, opaque); - 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) -{ - const char *serverout; - unsigned int serveroutlen; - int err; - char *clientdata = NULL; - RedsSASL *sasl = &stream->priv->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 */ - reds_stream_async_read(stream, (uint8_t *)&sasl->len, sizeof(uint32_t), - read_cb, opaque); - 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) -{ - RedsSASL *sasl = &stream->priv->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); - reds_stream_async_read(stream, (uint8_t *)sasl->data, sasl->len, - read_cb, opaque); - - return REDS_SASL_ERROR_OK; -} - -bool reds_sasl_handle_auth_mechname(RedsStream *stream, AsyncReadDone read_cb, void *opaque) -{ - RedsSASL *sasl = &stream->priv->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); - - reds_stream_async_read(stream, (uint8_t *)&sasl->len, sizeof(uint32_t), - read_cb, opaque); - - return TRUE; -} - -bool reds_sasl_handle_auth_mechlen(RedsStream *stream, AsyncReadDone read_cb, void *opaque) -{ - RedsSASL *sasl = &stream->priv->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"); - reds_stream_async_read(stream, (uint8_t *)sasl->mechname, sasl->len, - read_cb, opaque); - - 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; - RedsSASL *sasl = &stream->priv->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->priv->ssl) { - sasl_ssf_t ssf; - - ssf = SSL_get_cipher_bits(stream->priv->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->priv->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"); - reds_stream_async_read(stream, (uint8_t *)&sasl->len, sizeof(uint32_t), - read_cb, opaque); - - 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 deleted file mode 100644 index 6cbbbbbe..00000000 --- a/server/reds_stream.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - Copyright (C) 2009, 2013 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#ifndef _H_REDS_STREAM -#define _H_REDS_STREAM - -#include "spice.h" -#include "common/mem.h" - -#include - -#include - -typedef void (*AsyncReadDone)(void *opaque); -typedef void (*AsyncReadError)(void *opaque, int err); - -typedef struct RedsStream RedsStream; -typedef struct RedsStreamPrivate RedsStreamPrivate; - -struct RedsStream { - int socket; - SpiceWatch *watch; - - /* set it to TRUE if you shutdown the socket. shutdown read doesn't work as accepted - - receive may return data afterward. check the flag before calling receive*/ - int shutdown; - - RedsStreamPrivate *priv; -}; - -typedef enum { - REDS_STREAM_SSL_STATUS_OK, - REDS_STREAM_SSL_STATUS_ERROR, - REDS_STREAM_SSL_STATUS_WAIT_FOR_READ, - REDS_STREAM_SSL_STATUS_WAIT_FOR_WRITE -} RedsStreamSslStatus; - -/* any thread */ -ssize_t reds_stream_read(RedsStream *s, void *buf, size_t nbyte); -void reds_stream_async_read(RedsStream *stream, uint8_t *data, size_t size, - AsyncReadDone read_done_cb, void *opaque); -void reds_stream_set_async_error_handler(RedsStream *stream, - AsyncReadError error_handler); -ssize_t reds_stream_write(RedsStream *s, const void *buf, size_t nbyte); -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); -void reds_stream_remove_watch(RedsStream* s); -void reds_stream_set_channel(RedsStream *stream, int connection_id, - int channel_type, int channel_id); -RedsStream *reds_stream_new(int socket); -bool reds_stream_is_ssl(RedsStream *stream); -RedsStreamSslStatus reds_stream_ssl_accept(RedsStream *stream); -int reds_stream_enable_ssl(RedsStream *stream, SSL_CTX *ctx); -void reds_stream_set_info_flag(RedsStream *stream, unsigned int flag); - -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 -- cgit