mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 22:48:19 +00:00
server: add SASL support
We introduce 2 public functions to integrate with the library user. spice_server_set_sasl() - turn on SASL spice_server_set_sasl_appname() - specify the name of the app (It is used for where to find the default configuration file) The patch for QEMU is on its way. https://bugs.freedesktop.org/show_bug.cgi?id=34795
This commit is contained in:
parent
f4dddc50f0
commit
8f9cbd19db
718
server/reds.c
718
server/reds.c
@ -16,6 +16,8 @@
|
||||
License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
@ -38,6 +40,9 @@
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#if HAVE_SASL
|
||||
#include <sasl/sasl.h>
|
||||
#endif
|
||||
|
||||
#include "spice.h"
|
||||
#include "spice-experimental.h"
|
||||
@ -53,7 +58,6 @@
|
||||
#include <spice/stats.h>
|
||||
#include "stat.h"
|
||||
#include "ring.h"
|
||||
#include "config.h"
|
||||
#include "demarshallers.h"
|
||||
#include "marshaller.h"
|
||||
#include "generated_marshallers.h"
|
||||
@ -83,6 +87,10 @@ static int spice_secure_port = -1;
|
||||
static char spice_addr[256];
|
||||
static int spice_family = PF_UNSPEC;
|
||||
static char *default_renderer = "sw";
|
||||
static int sasl_enabled = 0; // sasl disabled by default
|
||||
#if HAVE_SASL
|
||||
static char *sasl_appname = NULL; // default to "spice" if NULL
|
||||
#endif
|
||||
|
||||
static int ticketing_enabled = 1; //Ticketing is enabled by default
|
||||
static pthread_mutex_t *lock_cs;
|
||||
@ -1349,15 +1357,20 @@ static void reds_channel_set_common_caps(Channel *channel, int cap, int active)
|
||||
channel->common_caps = spice_renew(uint32_t, channel->common_caps, channel->num_common_caps);
|
||||
memset(channel->common_caps + nbefore, 0,
|
||||
(channel->num_common_caps - nbefore) * sizeof(uint32_t));
|
||||
if (active)
|
||||
if (active) {
|
||||
channel->common_caps[n] |= (1 << cap);
|
||||
else
|
||||
} else {
|
||||
channel->common_caps[n] &= ~(1 << cap);
|
||||
}
|
||||
}
|
||||
|
||||
void reds_channel_init_auth_caps(Channel *channel)
|
||||
{
|
||||
reds_channel_set_common_caps(channel, SPICE_COMMON_CAP_AUTH_SPICE, TRUE);
|
||||
if (sasl_enabled) {
|
||||
reds_channel_set_common_caps(channel, SPICE_COMMON_CAP_AUTH_SASL, TRUE);
|
||||
} else {
|
||||
reds_channel_set_common_caps(channel, SPICE_COMMON_CAP_AUTH_SPICE, TRUE);
|
||||
}
|
||||
reds_channel_set_common_caps(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION, TRUE);
|
||||
}
|
||||
|
||||
@ -1486,7 +1499,7 @@ static void reds_handle_main_link(RedLinkInfo *link)
|
||||
link_mess = link->link_mess;
|
||||
reds_disconnect();
|
||||
|
||||
if (!link_mess->connection_id) {
|
||||
if (link_mess->connection_id == 0) {
|
||||
reds_send_link_result(link, SPICE_LINK_ERR_OK);
|
||||
while((connection_id = rand()) == 0);
|
||||
reds->agent_state.num_tokens = 0;
|
||||
@ -1670,6 +1683,104 @@ static inline void async_read_clear_handlers(AsyncRead *obj)
|
||||
reds_stream_remove_watch(obj->stream);
|
||||
}
|
||||
|
||||
#if HAVE_SASL
|
||||
static int sync_write_u8(RedsStream *s, uint8_t n)
|
||||
{
|
||||
return sync_write(s, &n, sizeof(uint8_t));
|
||||
}
|
||||
|
||||
static int sync_write_u32(RedsStream *s, uint32_t n)
|
||||
{
|
||||
return sync_write(s, &n, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
ssize_t reds_stream_sasl_write(RedsStream *s, const void *buf, size_t nbyte)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
if (!s->sasl.encoded) {
|
||||
int err;
|
||||
err = sasl_encode(s->sasl.conn, (char *)buf, nbyte,
|
||||
(const char **)&s->sasl.encoded,
|
||||
&s->sasl.encodedLength);
|
||||
if (err != SASL_OK) {
|
||||
red_printf("sasl_encode error: %d", err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (s->sasl.encodedLength == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!s->sasl.encoded) {
|
||||
red_printf("sasl_encode didn't return a buffer!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
s->sasl.encodedOffset = 0;
|
||||
}
|
||||
|
||||
ret = s->write(s, s->sasl.encoded + s->sasl.encodedOffset,
|
||||
s->sasl.encodedLength - s->sasl.encodedOffset);
|
||||
|
||||
if (ret <= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
s->sasl.encodedOffset += ret;
|
||||
if (s->sasl.encodedOffset == s->sasl.encodedLength) {
|
||||
s->sasl.encoded = NULL;
|
||||
s->sasl.encodedOffset = s->sasl.encodedLength = 0;
|
||||
return nbyte;
|
||||
}
|
||||
|
||||
/* we didn't flush the encoded buffer */
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static ssize_t reds_stream_sasl_read(RedsStream *s, void *buf, size_t nbyte)
|
||||
{
|
||||
uint8_t encoded[4096];
|
||||
const char *decoded;
|
||||
unsigned int decodedlen;
|
||||
int err;
|
||||
int n;
|
||||
|
||||
n = spice_buffer_copy(&s->sasl.inbuffer, buf, nbyte);
|
||||
if (n > 0) {
|
||||
spice_buffer_remove(&s->sasl.inbuffer, n);
|
||||
if (n == nbyte)
|
||||
return n;
|
||||
nbyte -= n;
|
||||
buf += n;
|
||||
}
|
||||
|
||||
n = s->read(s, encoded, sizeof(encoded));
|
||||
if (n <= 0) {
|
||||
return n;
|
||||
}
|
||||
|
||||
err = sasl_decode(s->sasl.conn,
|
||||
(char *)encoded, n,
|
||||
&decoded, &decodedlen);
|
||||
if (err != SASL_OK) {
|
||||
red_printf("sasl_decode error: %d", err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decodedlen == 0) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
n = MIN(nbyte, decodedlen);
|
||||
memcpy(buf, decoded, n);
|
||||
spice_buffer_append(&s->sasl.inbuffer, decoded + n, decodedlen - n);
|
||||
return n;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void async_read_handler(int fd, int event, void *data)
|
||||
{
|
||||
AsyncRead *obj = (AsyncRead *)data;
|
||||
@ -1722,16 +1833,537 @@ static void reds_get_spice_ticket(RedLinkInfo *link)
|
||||
async_read_handler(0, 0, &link->asyc_read);
|
||||
}
|
||||
|
||||
#if HAVE_SASL
|
||||
static char *addr_to_string(const char *format,
|
||||
struct sockaddr *sa,
|
||||
socklen_t salen) {
|
||||
char *addr;
|
||||
char host[NI_MAXHOST];
|
||||
char serv[NI_MAXSERV];
|
||||
int err;
|
||||
size_t addrlen;
|
||||
|
||||
if ((err = getnameinfo(sa, salen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
|
||||
red_printf("Cannot resolve address %d: %s",
|
||||
err, gai_strerror(err));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Enough for the existing format + the 2 vars we're
|
||||
* substituting in. */
|
||||
addrlen = strlen(format) + strlen(host) + strlen(serv);
|
||||
addr = spice_malloc(addrlen + 1);
|
||||
snprintf(addr, addrlen, format, host, serv);
|
||||
addr[addrlen] = '\0';
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static int auth_sasl_check_ssf(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;
|
||||
red_printf("negotiated an SSF of %d", ssf);
|
||||
if (ssf < 56) {
|
||||
return 0; /* 56 is good for Kerberos */
|
||||
}
|
||||
|
||||
*runSSF = 1;
|
||||
|
||||
/* We have a SSF that's good enough */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step Msg
|
||||
*
|
||||
* Input from client:
|
||||
*
|
||||
* u32 clientin-length
|
||||
* u8-array clientin-string
|
||||
*
|
||||
* Output to client:
|
||||
*
|
||||
* u32 serverout-length
|
||||
* u8-array serverout-strin
|
||||
* u8 continue
|
||||
*/
|
||||
#define SASL_DATA_MAX_LEN (1024 * 1024)
|
||||
|
||||
static void reds_handle_auth_sasl_steplen(void *opaque);
|
||||
|
||||
static void reds_handle_auth_sasl_step(void *opaque)
|
||||
{
|
||||
const char *serverout;
|
||||
unsigned int serveroutlen;
|
||||
int err;
|
||||
char *clientdata = NULL;
|
||||
RedLinkInfo *link = (RedLinkInfo *)opaque;
|
||||
RedsSASL *sasl = &link->stream->sasl;
|
||||
uint32_t datalen = sasl->len;
|
||||
AsyncRead *obj = &link->asyc_read;
|
||||
|
||||
/* NB, distinction of NULL vs "" is *critical* in SASL */
|
||||
if (datalen) {
|
||||
clientdata = sasl->data;
|
||||
clientdata[datalen - 1] = '\0'; /* Wire includes '\0', but make sure */
|
||||
datalen--; /* Don't count NULL byte when passing to _start() */
|
||||
}
|
||||
|
||||
red_printf("Step using SASL Data %p (%d bytes)",
|
||||
clientdata, datalen);
|
||||
err = sasl_server_step(sasl->conn,
|
||||
clientdata,
|
||||
datalen,
|
||||
&serverout,
|
||||
&serveroutlen);
|
||||
if (err != SASL_OK &&
|
||||
err != SASL_CONTINUE) {
|
||||
red_printf("sasl step failed %d (%s)",
|
||||
err, sasl_errdetail(sasl->conn));
|
||||
goto authabort;
|
||||
}
|
||||
|
||||
if (serveroutlen > SASL_DATA_MAX_LEN) {
|
||||
red_printf("sasl step reply data too long %d",
|
||||
serveroutlen);
|
||||
goto authabort;
|
||||
}
|
||||
|
||||
red_printf("SASL return data %d bytes, %p", serveroutlen, serverout);
|
||||
|
||||
if (serveroutlen) {
|
||||
serveroutlen += 1;
|
||||
sync_write(link->stream, &serveroutlen, sizeof(uint32_t));
|
||||
sync_write(link->stream, serverout, serveroutlen);
|
||||
} else {
|
||||
sync_write(link->stream, &serveroutlen, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
/* Whether auth is complete */
|
||||
sync_write_u8(link->stream, err == SASL_CONTINUE ? 0 : 1);
|
||||
|
||||
if (err == SASL_CONTINUE) {
|
||||
red_printf("%s", "Authentication must continue (step)");
|
||||
/* Wait for step length */
|
||||
obj->now = (uint8_t *)&sasl->len;
|
||||
obj->end = obj->now + sizeof(uint32_t);
|
||||
obj->done = reds_handle_auth_sasl_steplen;
|
||||
async_read_handler(0, 0, &link->asyc_read);
|
||||
} else {
|
||||
int ssf;
|
||||
|
||||
if (auth_sasl_check_ssf(sasl, &ssf) == 0) {
|
||||
red_printf("Authentication rejected for weak SSF");
|
||||
goto authreject;
|
||||
}
|
||||
|
||||
red_printf("Authentication successful");
|
||||
sync_write_u32(link->stream, SPICE_LINK_ERR_OK); /* Accept auth */
|
||||
|
||||
/*
|
||||
* Delay writing in SSF encoded until now
|
||||
*/
|
||||
sasl->runSSF = ssf;
|
||||
link->stream->writev = NULL; /* make sure writev isn't called directly anymore */
|
||||
|
||||
reds_handle_link(link);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
authreject:
|
||||
sync_write_u32(link->stream, 1); /* Reject auth */
|
||||
sync_write_u32(link->stream, sizeof("Authentication failed"));
|
||||
sync_write(link->stream, "Authentication failed", sizeof("Authentication failed"));
|
||||
|
||||
authabort:
|
||||
reds_link_free(link);
|
||||
return;
|
||||
}
|
||||
|
||||
static void reds_handle_auth_sasl_steplen(void *opaque)
|
||||
{
|
||||
RedLinkInfo *link = (RedLinkInfo *)opaque;
|
||||
AsyncRead *obj = &link->asyc_read;
|
||||
RedsSASL *sasl = &link->stream->sasl;
|
||||
|
||||
red_printf("Got steplen %d", sasl->len);
|
||||
if (sasl->len > SASL_DATA_MAX_LEN) {
|
||||
red_printf("Too much SASL data %d", sasl->len);
|
||||
reds_link_free(link);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sasl->len == 0) {
|
||||
return reds_handle_auth_sasl_step(opaque);
|
||||
} else {
|
||||
sasl->data = spice_realloc(sasl->data, sasl->len);
|
||||
obj->now = (uint8_t *)sasl->data;
|
||||
obj->end = obj->now + sasl->len;
|
||||
obj->done = reds_handle_auth_sasl_step;
|
||||
async_read_handler(0, 0, &link->asyc_read);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Start Msg
|
||||
*
|
||||
* Input from client:
|
||||
*
|
||||
* u32 clientin-length
|
||||
* u8-array clientin-string
|
||||
*
|
||||
* Output to client:
|
||||
*
|
||||
* u32 serverout-length
|
||||
* u8-array serverout-strin
|
||||
* u8 continue
|
||||
*/
|
||||
|
||||
|
||||
static void reds_handle_auth_sasl_start(void *opaque)
|
||||
{
|
||||
RedLinkInfo *link = (RedLinkInfo *)opaque;
|
||||
AsyncRead *obj = &link->asyc_read;
|
||||
const char *serverout;
|
||||
unsigned int serveroutlen;
|
||||
int err;
|
||||
char *clientdata = NULL;
|
||||
RedsSASL *sasl = &link->stream->sasl;
|
||||
uint32_t datalen = sasl->len;
|
||||
|
||||
/* NB, distinction of NULL vs "" is *critical* in SASL */
|
||||
if (datalen) {
|
||||
clientdata = sasl->data;
|
||||
clientdata[datalen - 1] = '\0'; /* Should be on wire, but make sure */
|
||||
datalen--; /* Don't count NULL byte when passing to _start() */
|
||||
}
|
||||
|
||||
red_printf("Start SASL auth with mechanism %s. Data %p (%d bytes)",
|
||||
sasl->mechlist, clientdata, datalen);
|
||||
err = sasl_server_start(sasl->conn,
|
||||
sasl->mechlist,
|
||||
clientdata,
|
||||
datalen,
|
||||
&serverout,
|
||||
&serveroutlen);
|
||||
if (err != SASL_OK &&
|
||||
err != SASL_CONTINUE) {
|
||||
red_printf("sasl start failed %d (%s)",
|
||||
err, sasl_errdetail(sasl->conn));
|
||||
goto authabort;
|
||||
}
|
||||
|
||||
if (serveroutlen > SASL_DATA_MAX_LEN) {
|
||||
red_printf("sasl start reply data too long %d",
|
||||
serveroutlen);
|
||||
goto authabort;
|
||||
}
|
||||
|
||||
red_printf("SASL return data %d bytes, %p", serveroutlen, serverout);
|
||||
|
||||
if (serveroutlen) {
|
||||
serveroutlen += 1;
|
||||
sync_write(link->stream, &serveroutlen, sizeof(uint32_t));
|
||||
sync_write(link->stream, serverout, serveroutlen);
|
||||
} else {
|
||||
sync_write(link->stream, &serveroutlen, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
/* Whether auth is complete */
|
||||
sync_write_u8(link->stream, err == SASL_CONTINUE ? 0 : 1);
|
||||
|
||||
if (err == SASL_CONTINUE) {
|
||||
red_printf("%s", "Authentication must continue (start)");
|
||||
/* Wait for step length */
|
||||
obj->now = (uint8_t *)&sasl->len;
|
||||
obj->end = obj->now + sizeof(uint32_t);
|
||||
obj->done = reds_handle_auth_sasl_steplen;
|
||||
async_read_handler(0, 0, &link->asyc_read);
|
||||
} else {
|
||||
int ssf;
|
||||
|
||||
if (auth_sasl_check_ssf(sasl, &ssf) == 0) {
|
||||
red_printf("Authentication rejected for weak SSF");
|
||||
goto authreject;
|
||||
}
|
||||
|
||||
red_printf("Authentication successful");
|
||||
sync_write_u32(link->stream, SPICE_LINK_ERR_OK); /* Accept auth */
|
||||
|
||||
/*
|
||||
* Delay writing in SSF encoded until now
|
||||
*/
|
||||
sasl->runSSF = ssf;
|
||||
link->stream->writev = NULL; /* make sure writev isn't called directly anymore */
|
||||
|
||||
reds_handle_link(link);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
authreject:
|
||||
sync_write_u32(link->stream, 1); /* Reject auth */
|
||||
sync_write_u32(link->stream, sizeof("Authentication failed"));
|
||||
sync_write(link->stream, "Authentication failed", sizeof("Authentication failed"));
|
||||
|
||||
authabort:
|
||||
reds_link_free(link);
|
||||
return;
|
||||
}
|
||||
|
||||
static void reds_handle_auth_startlen(void *opaque)
|
||||
{
|
||||
RedLinkInfo *link = (RedLinkInfo *)opaque;
|
||||
AsyncRead *obj = &link->asyc_read;
|
||||
RedsSASL *sasl = &link->stream->sasl;
|
||||
|
||||
red_printf("Got client start len %d", sasl->len);
|
||||
if (sasl->len > SASL_DATA_MAX_LEN) {
|
||||
red_printf("Too much SASL data %d", sasl->len);
|
||||
reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
|
||||
reds_link_free(link);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sasl->len == 0) {
|
||||
reds_handle_auth_sasl_start(opaque);
|
||||
return;
|
||||
}
|
||||
|
||||
sasl->data = spice_realloc(sasl->data, sasl->len);
|
||||
obj->now = (uint8_t *)sasl->data;
|
||||
obj->end = obj->now + sasl->len;
|
||||
obj->done = reds_handle_auth_sasl_start;
|
||||
async_read_handler(0, 0, &link->asyc_read);
|
||||
}
|
||||
|
||||
static void reds_handle_auth_mechname(void *opaque)
|
||||
{
|
||||
RedLinkInfo *link = (RedLinkInfo *)opaque;
|
||||
AsyncRead *obj = &link->asyc_read;
|
||||
RedsSASL *sasl = &link->stream->sasl;
|
||||
|
||||
sasl->mechname[sasl->len] = '\0';
|
||||
red_printf("Got client mechname '%s' check against '%s'",
|
||||
sasl->mechname, sasl->mechlist);
|
||||
|
||||
if (strncmp(sasl->mechlist, sasl->mechname, sasl->len) == 0) {
|
||||
if (sasl->mechlist[sasl->len] != '\0' &&
|
||||
sasl->mechlist[sasl->len] != ',') {
|
||||
red_printf("One %d", sasl->mechlist[sasl->len]);
|
||||
reds_link_free(link);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
char *offset = strstr(sasl->mechlist, sasl->mechname);
|
||||
red_printf("Two %p", offset);
|
||||
if (!offset) {
|
||||
reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
|
||||
return;
|
||||
}
|
||||
red_printf("Two '%s'", offset);
|
||||
if (offset[-1] != ',' ||
|
||||
(offset[sasl->len] != '\0'&&
|
||||
offset[sasl->len] != ',')) {
|
||||
reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
free(sasl->mechlist);
|
||||
sasl->mechlist = strdup(sasl->mechname);
|
||||
|
||||
red_printf("Validated mechname '%s'", sasl->mechname);
|
||||
|
||||
obj->now = (uint8_t *)&sasl->len;
|
||||
obj->end = obj->now + sizeof(uint32_t);
|
||||
obj->done = reds_handle_auth_startlen;
|
||||
async_read_handler(0, 0, &link->asyc_read);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void reds_handle_auth_mechlen(void *opaque)
|
||||
{
|
||||
RedLinkInfo *link = (RedLinkInfo *)opaque;
|
||||
AsyncRead *obj = &link->asyc_read;
|
||||
RedsSASL *sasl = &link->stream->sasl;
|
||||
|
||||
if (sasl->len < 1 || sasl->len > 100) {
|
||||
red_printf("Got bad client mechname len %d", sasl->len);
|
||||
reds_link_free(link);
|
||||
return;
|
||||
}
|
||||
|
||||
sasl->mechname = spice_malloc(sasl->len + 1);
|
||||
|
||||
red_printf("Wait for client mechname");
|
||||
obj->now = (uint8_t *)sasl->mechname;
|
||||
obj->end = obj->now + sasl->len;
|
||||
obj->done = reds_handle_auth_mechname;
|
||||
async_read_handler(0, 0, &link->asyc_read);
|
||||
}
|
||||
|
||||
static void reds_start_auth_sasl(RedLinkInfo *link)
|
||||
{
|
||||
const char *mechlist = NULL;
|
||||
sasl_security_properties_t secprops;
|
||||
int err;
|
||||
char *localAddr, *remoteAddr;
|
||||
int mechlistlen;
|
||||
AsyncRead *obj = &link->asyc_read;
|
||||
RedsSASL *sasl = &link->stream->sasl;
|
||||
|
||||
/* Get local & remote client addresses in form IPADDR;PORT */
|
||||
if (!(localAddr = addr_to_string("%s;%s", &link->stream->info.laddr, link->stream->info.llen))) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!(remoteAddr = addr_to_string("%s;%s", &link->stream->info.paddr, link->stream->info.plen))) {
|
||||
free(localAddr);
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = sasl_server_new("spice",
|
||||
NULL, /* FQDN - just delegates to gethostname */
|
||||
NULL, /* User realm */
|
||||
localAddr,
|
||||
remoteAddr,
|
||||
NULL, /* Callbacks, not needed */
|
||||
SASL_SUCCESS_DATA,
|
||||
&sasl->conn);
|
||||
free(localAddr);
|
||||
free(remoteAddr);
|
||||
localAddr = remoteAddr = NULL;
|
||||
|
||||
if (err != SASL_OK) {
|
||||
red_printf("sasl context setup failed %d (%s)",
|
||||
err, sasl_errstring(err, NULL, NULL));
|
||||
sasl->conn = NULL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Inform SASL that we've got an external SSF layer from TLS */
|
||||
if (link->stream->ssl) {
|
||||
sasl_ssf_t ssf;
|
||||
|
||||
ssf = SSL_get_cipher_bits(link->stream->ssl, NULL);
|
||||
err = sasl_setprop(sasl->conn, SASL_SSF_EXTERNAL, &ssf);
|
||||
if (err != SASL_OK) {
|
||||
red_printf("cannot set SASL external SSF %d (%s)",
|
||||
err, sasl_errstring(err, NULL, NULL));
|
||||
sasl_dispose(&sasl->conn);
|
||||
sasl->conn = NULL;
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
sasl->wantSSF = 1;
|
||||
}
|
||||
|
||||
memset(&secprops, 0, sizeof secprops);
|
||||
/* Inform SASL that we've got an external SSF layer from TLS */
|
||||
if (link->stream->ssl) {
|
||||
/* If we've got TLS (or UNIX domain sock), we don't care about SSF */
|
||||
secprops.min_ssf = 0;
|
||||
secprops.max_ssf = 0;
|
||||
secprops.maxbufsize = 8192;
|
||||
secprops.security_flags = 0;
|
||||
} else {
|
||||
/* Plain TCP, better get an SSF layer */
|
||||
secprops.min_ssf = 56; /* Good enough to require kerberos */
|
||||
secprops.max_ssf = 100000; /* Arbitrary big number */
|
||||
secprops.maxbufsize = 8192;
|
||||
/* Forbid any anonymous or trivially crackable auth */
|
||||
secprops.security_flags =
|
||||
SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
|
||||
}
|
||||
|
||||
err = sasl_setprop(sasl->conn, SASL_SEC_PROPS, &secprops);
|
||||
if (err != SASL_OK) {
|
||||
red_printf("cannot set SASL security props %d (%s)",
|
||||
err, sasl_errstring(err, NULL, NULL));
|
||||
sasl_dispose(&sasl->conn);
|
||||
sasl->conn = NULL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = sasl_listmech(sasl->conn,
|
||||
NULL, /* Don't need to set user */
|
||||
"", /* Prefix */
|
||||
",", /* Separator */
|
||||
"", /* Suffix */
|
||||
&mechlist,
|
||||
NULL,
|
||||
NULL);
|
||||
if (err != SASL_OK) {
|
||||
red_printf("cannot list SASL mechanisms %d (%s)",
|
||||
err, sasl_errdetail(sasl->conn));
|
||||
sasl_dispose(&sasl->conn);
|
||||
sasl->conn = NULL;
|
||||
goto error;
|
||||
}
|
||||
red_printf("Available mechanisms for client: '%s'", mechlist);
|
||||
|
||||
sasl->mechlist = strdup(mechlist);
|
||||
|
||||
mechlistlen = strlen(mechlist);
|
||||
if (!sync_write(link->stream, &mechlistlen, sizeof(uint32_t))
|
||||
|| !sync_write(link->stream, sasl->mechlist, mechlistlen)) {
|
||||
red_printf("SASL mechanisms write error");
|
||||
goto error;
|
||||
}
|
||||
|
||||
red_printf("Wait for client mechname length");
|
||||
obj->now = (uint8_t *)&sasl->len;
|
||||
obj->end = obj->now + sizeof(uint32_t);
|
||||
obj->done = reds_handle_auth_mechlen;
|
||||
async_read_handler(0, 0, &link->asyc_read);
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
reds_link_free(link);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void reds_handle_auth_mechanism(void *opaque)
|
||||
{
|
||||
RedLinkInfo *link = (RedLinkInfo *)opaque;
|
||||
|
||||
red_printf("Auth method: %d", link->auth_mechanism.auth_mechanism);
|
||||
|
||||
if (link->auth_mechanism.auth_mechanism == SPICE_COMMON_CAP_AUTH_SPICE) {
|
||||
if (link->auth_mechanism.auth_mechanism == SPICE_COMMON_CAP_AUTH_SPICE
|
||||
&& !sasl_enabled
|
||||
) {
|
||||
reds_get_spice_ticket(link);
|
||||
#if HAVE_SASL
|
||||
} else if (link->auth_mechanism.auth_mechanism == SPICE_COMMON_CAP_AUTH_SASL) {
|
||||
red_printf("Starting SASL");
|
||||
reds_start_auth_sasl(link);
|
||||
#endif
|
||||
} else {
|
||||
red_printf("Unknown auth method, disconnecting");
|
||||
if (sasl_enabled) {
|
||||
red_printf("Your client doesn't handle SASL?");
|
||||
}
|
||||
reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
|
||||
reds_link_free(link);
|
||||
}
|
||||
@ -1783,6 +2415,11 @@ static void reds_handle_read_link_done(void *opaque)
|
||||
}
|
||||
|
||||
if (!auth_selection) {
|
||||
if (sasl_enabled) {
|
||||
red_printf("SASL enabled, but peer supports only spice authentication");
|
||||
reds_send_link_error(link, SPICE_LINK_ERR_VERSION_MISMATCH);
|
||||
return;
|
||||
}
|
||||
red_printf("Peer doesn't support AUTH selection");
|
||||
reds_get_spice_ticket(link);
|
||||
} else {
|
||||
@ -2842,6 +3479,16 @@ static int do_spice_init(SpiceCoreInterface *core_interface)
|
||||
if (reds->secure_listen_socket != -1) {
|
||||
reds_init_ssl();
|
||||
}
|
||||
#if HAVE_SASL
|
||||
int saslerr;
|
||||
if ((saslerr = sasl_server_init(NULL, sasl_appname ?
|
||||
sasl_appname : "spice")) != SASL_OK) {
|
||||
red_error("Failed to initialize SASL auth %s",
|
||||
sasl_errstring(saslerr, NULL, NULL));
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
|
||||
reds->main_channel = NULL;
|
||||
inputs_init();
|
||||
|
||||
@ -2935,6 +3582,29 @@ __visible__ int spice_server_set_noauth(SpiceServer *s)
|
||||
return 0;
|
||||
}
|
||||
|
||||
__visible__ int spice_server_set_sasl(SpiceServer *s, int enabled)
|
||||
{
|
||||
ASSERT(reds == s);
|
||||
#if HAVE_SASL
|
||||
sasl_enabled = enabled;
|
||||
return 0;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
__visible__ int spice_server_set_sasl_appname(SpiceServer *s, const char *appname)
|
||||
{
|
||||
ASSERT(reds == s);
|
||||
#if HAVE_SASL
|
||||
free(sasl_appname);
|
||||
sasl_appname = strdup(appname);
|
||||
return 0;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
__visible__ int spice_server_set_ticket(SpiceServer *s,
|
||||
const char *passwd, int lifetime,
|
||||
int fail_if_connected,
|
||||
@ -3210,12 +3880,30 @@ __visible__ int spice_server_migrate_switch(SpiceServer *s)
|
||||
|
||||
ssize_t reds_stream_read(RedsStream *s, void *buf, size_t nbyte)
|
||||
{
|
||||
return s->read(s, buf, nbyte);
|
||||
ssize_t ret;
|
||||
|
||||
#if HAVE_SASL
|
||||
if (s->sasl.conn && s->sasl.runSSF) {
|
||||
ret = reds_stream_sasl_read(s, buf, nbyte);
|
||||
} else
|
||||
#endif
|
||||
ret = s->read(s, buf, nbyte);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t reds_stream_write(RedsStream *s, const void *buf, size_t nbyte)
|
||||
{
|
||||
return s->write(s, buf, nbyte);
|
||||
ssize_t ret;
|
||||
|
||||
#if HAVE_SASL
|
||||
if (s->sasl.conn && s->sasl.runSSF) {
|
||||
ret = reds_stream_sasl_write(s, buf, nbyte);
|
||||
} else
|
||||
#endif
|
||||
ret = s->write(s, buf, nbyte);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t reds_stream_writev(RedsStream *s, const struct iovec *iov, int iovcnt)
|
||||
@ -3246,6 +3934,20 @@ void reds_stream_free(RedsStream *s)
|
||||
|
||||
reds_stream_channel_event(s, SPICE_CHANNEL_EVENT_DISCONNECTED);
|
||||
|
||||
#if HAVE_SASL
|
||||
if (s->sasl.conn) {
|
||||
s->sasl.runSSF = s->sasl.wantSSF = 0;
|
||||
s->sasl.len = 0;
|
||||
s->sasl.encodedLength = s->sasl.encodedOffset = 0;
|
||||
s->sasl.encoded = NULL;
|
||||
free(s->sasl.mechlist);
|
||||
free(s->sasl.mechname);
|
||||
s->sasl.mechlist = NULL;
|
||||
sasl_dispose(&s->sasl.conn);
|
||||
s->sasl.conn = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (s->ssl) {
|
||||
SSL_free(s->ssl);
|
||||
}
|
||||
|
||||
@ -18,10 +18,16 @@
|
||||
#ifndef _H_REDS
|
||||
#define _H_REDS
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <sys/uio.h>
|
||||
#include <spice/vd_agent.h>
|
||||
#if HAVE_SASL
|
||||
#include <sasl/sasl.h>
|
||||
#endif
|
||||
|
||||
#include "common/marshaller.h"
|
||||
#include "common/messages.h"
|
||||
#include "spice.h"
|
||||
@ -30,6 +36,35 @@
|
||||
|
||||
typedef struct RedsStream RedsStream;
|
||||
|
||||
#if HAVE_SASL
|
||||
typedef struct 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 RedsStream {
|
||||
int socket;
|
||||
SpiceWatch *watch;
|
||||
@ -39,6 +74,10 @@ struct RedsStream {
|
||||
int shutdown;
|
||||
SSL *ssl;
|
||||
|
||||
#if HAVE_SASL
|
||||
RedsSASL sasl;
|
||||
#endif
|
||||
|
||||
SpiceChannelEventInfo info;
|
||||
|
||||
/* private */
|
||||
|
||||
@ -374,6 +374,8 @@ int spice_server_set_compat_version(SpiceServer *s,
|
||||
int spice_server_set_port(SpiceServer *s, int port);
|
||||
void spice_server_set_addr(SpiceServer *s, const char *addr, int flags);
|
||||
int spice_server_set_noauth(SpiceServer *s);
|
||||
int spice_server_set_sasl(SpiceServer *s, int enabled);
|
||||
int spice_server_set_sasl_appname(SpiceServer *s, const char *appname);
|
||||
int spice_server_set_ticket(SpiceServer *s, const char *passwd, int lifetime,
|
||||
int fail_if_connected, int disconnect_if_connected);
|
||||
int spice_server_set_tls(SpiceServer *s, int port,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user