mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 22:48:19 +00:00
The include directory is specified with the -I which is the directory used directly by #include<>. Signed-off-by: Frediano Ziglio <fziglio@redhat.com> Acked-by: Pavel Grunt <pgrunt@redhat.com>
1134 lines
31 KiB
C
1134 lines
31 KiB
C
/* -*- 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "main-dispatcher.h"
|
|
#include "red-common.h"
|
|
#include <common/log.h>
|
|
#include "reds-stream.h"
|
|
#include "reds.h"
|
|
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <openssl/err.h>
|
|
|
|
struct AsyncRead {
|
|
RedsStream *stream;
|
|
void *opaque;
|
|
uint8_t *now;
|
|
uint8_t *end;
|
|
AsyncReadDone done;
|
|
AsyncReadError error;
|
|
};
|
|
typedef struct AsyncRead AsyncRead;
|
|
|
|
#if HAVE_SASL
|
|
#include <sasl/sasl.h>
|
|
|
|
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);
|
|
|
|
RedsState *reds;
|
|
};
|
|
|
|
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) {
|
|
reds_core_watch_remove(s->priv->reds, 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;
|
|
}
|
|
|
|
int reds_stream_get_family(const RedsStream *s)
|
|
{
|
|
spice_return_val_if_fail(s != NULL, -1);
|
|
|
|
if (s->socket == -1)
|
|
return -1;
|
|
|
|
return s->priv->info->laddr_ext.ss_family;
|
|
}
|
|
|
|
int reds_stream_is_plain_unix(const RedsStream *s)
|
|
{
|
|
spice_return_val_if_fail(s != NULL, FALSE);
|
|
|
|
if (reds_stream_get_family(s) != AF_UNIX)
|
|
return FALSE;
|
|
|
|
#if HAVE_SASL
|
|
if (s->priv->sasl.conn)
|
|
return FALSE;
|
|
#endif
|
|
if (s->priv->ssl)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
int reds_stream_send_msgfd(RedsStream *stream, int fd)
|
|
{
|
|
struct msghdr msgh = { 0, };
|
|
struct iovec iov;
|
|
int r;
|
|
|
|
const size_t fd_size = 1 * sizeof(int);
|
|
struct cmsghdr *cmsg;
|
|
union {
|
|
struct cmsghdr hdr;
|
|
char data[CMSG_SPACE(fd_size)];
|
|
} control;
|
|
|
|
spice_return_val_if_fail(reds_stream_is_plain_unix(stream), -1);
|
|
|
|
/* set the payload */
|
|
iov.iov_base = (char*)"@";
|
|
iov.iov_len = 1;
|
|
msgh.msg_iovlen = 1;
|
|
msgh.msg_iov = &iov;
|
|
|
|
if (fd != -1) {
|
|
msgh.msg_control = control.data;
|
|
msgh.msg_controllen = sizeof(control.data);
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msgh);
|
|
cmsg->cmsg_len = CMSG_LEN(fd_size);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
memcpy(CMSG_DATA(cmsg), &fd, fd_size);
|
|
}
|
|
|
|
do {
|
|
r = sendmsg(stream->socket, &msgh, MSG_NOSIGNAL);
|
|
} while (r < 0 && (errno == EINTR || errno == EAGAIN));
|
|
|
|
return r;
|
|
}
|
|
|
|
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)
|
|
{
|
|
RedsState *reds = s->priv->reds;
|
|
MainDispatcher *md = reds_get_main_dispatcher(reds);
|
|
main_dispatcher_channel_event(md, 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(RedsState *reds, int socket)
|
|
{
|
|
RedsStream *stream;
|
|
|
|
stream = spice_malloc0(sizeof(RedsStream) + sizeof(RedsStreamPrivate));
|
|
stream->priv = (RedsStreamPrivate *)(stream+1);
|
|
stream->priv->info = spice_new0(SpiceChannelEventInfo, 1);
|
|
stream->priv->reds = reds;
|
|
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;
|
|
RedsStream *stream = async->stream;
|
|
RedsState *reds = stream->priv->reds;
|
|
|
|
for (;;) {
|
|
int n = async->end - async->now;
|
|
|
|
spice_assert(n > 0);
|
|
n = reds_stream_read(stream, async->now, n);
|
|
if (n <= 0) {
|
|
if (n < 0) {
|
|
switch (errno) {
|
|
case EAGAIN:
|
|
if (!stream->watch) {
|
|
stream->watch = reds_core_watch_add(reds, 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
|