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); }
/* GStreamer thread * * We cannot use GStreamer's signals because they are not always run in * the main context. So use a callback (lower overhead) and have it pull * the sample to avoid a race with free_pipeline(). This means queuing the * decoded frames outside GStreamer. So while we're at it, also schedule * the frame display ourselves in schedule_frame(). */ static GstFlowReturn new_sample(GstAppSink *gstappsink, gpointer video_decoder) { SpiceGstDecoder *decoder = video_decoder; GstSample *sample = gst_app_sink_pull_sample(decoder->appsink); GstBuffer *buffer = sample ? gst_sample_get_buffer(sample) : NULL; if (sample) { g_mutex_lock(&decoder->queues_mutex); /* gst_app_sink_pull_sample() sometimes returns the same buffer twice * or buffers that have a modified, and thus unrecognizable, PTS. * Blindly removing frames from the decoding_queue until we find a * match would only empty the queue, resulting in later buffers not * finding a match either, etc. So check the buffer has a matching * frame first. */ SpiceGstFrame *gstframe; GList *l = g_queue_peek_head_link(decoder->decoding_queue); while (l) { gstframe = l->data; if (gstframe->timestamp == GST_BUFFER_PTS(buffer)) { /* The frame is now ready for display */ gstframe->sample = sample; g_queue_push_tail(decoder->display_queue, gstframe); /* Now that we know there is a match, remove it and the older * frames from the decoding queue. */ while ((gstframe = g_queue_pop_head(decoder->decoding_queue))) { if (gstframe->timestamp == GST_BUFFER_PTS(buffer)) { break; } /* The GStreamer pipeline dropped the corresponding * buffer. */ SPICE_DEBUG("the GStreamer pipeline dropped a frame"); free_gst_frame(gstframe); } break; } l = l->next; } if (!l) { spice_warning("got an unexpected decoded buffer!"); gst_sample_unref(sample); } g_mutex_unlock(&decoder->queues_mutex); schedule_frame(decoder); } else { spice_warning("GStreamer error: could not pull sample"); } return GST_FLOW_OK; }
/* main context */ static gboolean display_frame(gpointer video_decoder) { SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder; SpiceGstFrame *gstframe; GstCaps *caps; gint width, height; GstStructure *s; GstBuffer *buffer; GstMapInfo mapinfo; g_mutex_lock(&decoder->queues_mutex); decoder->timer_id = 0; gstframe = g_queue_pop_head(decoder->display_queue); g_mutex_unlock(&decoder->queues_mutex); /* If the queue is empty we don't even need to reschedule */ g_return_val_if_fail(gstframe, G_SOURCE_REMOVE); if (!gstframe->sample) { spice_warning("got a frame without a sample!"); goto error; } caps = gst_sample_get_caps(gstframe->sample); if (!caps) { spice_warning("GStreamer error: could not get the caps of the sample"); goto error; } s = gst_caps_get_structure(caps, 0); if (!gst_structure_get_int(s, "width", &width) || !gst_structure_get_int(s, "height", &height)) { spice_warning("GStreamer error: could not get the size of the frame"); goto error; } buffer = gst_sample_get_buffer(gstframe->sample); if (!gst_buffer_map(buffer, &mapinfo, GST_MAP_READ)) { spice_warning("GStreamer error: could not map the buffer"); goto error; } stream_display_frame(decoder->base.stream, gstframe->frame, width, height, mapinfo.data); gst_buffer_unmap(buffer, &mapinfo); error: free_gst_frame(gstframe); schedule_frame(decoder); return G_SOURCE_REMOVE; }
static char *addr_to_string(const char *format, struct sockaddr_storage *sa, socklen_t salen) { char *addr; char host[NI_MAXHOST]; char serv[NI_MAXSERV]; int err; size_t addrlen; if ((err = getnameinfo((struct sockaddr *)sa, salen, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { spice_warning("Cannot resolve address %d: %s", err, gai_strerror(err)); return NULL; } /* Enough for the existing format + the 2 vars we're * substituting in. */ addrlen = strlen(format) + strlen(host) + strlen(serv); addr = spice_malloc(addrlen + 1); snprintf(addr, addrlen, format, host, serv); addr[addrlen] = '\0'; return addr; }
static int spicevmc_red_channel_client_handle_message(RedChannelClient *rcc, uint16_t type, uint32_t size, uint8_t *msg) { SpiceVmcState *state; SpiceCharDeviceInstance *sin; SpiceCharDeviceInterface *sif; state = spicevmc_red_channel_client_get_state(rcc); sin = state->chardev_sin; sif = SPICE_CONTAINEROF(sin->base.sif, SpiceCharDeviceInterface, base); switch (type) { case SPICE_MSGC_SPICEVMC_DATA: spice_assert(state->recv_from_client_buf->buf == msg); state->recv_from_client_buf->buf_used = size; spice_char_device_write_buffer_add(state->chardev_st, state->recv_from_client_buf); state->recv_from_client_buf = NULL; break; case SPICE_MSGC_PORT_EVENT: if (size != sizeof(uint8_t)) { spice_warning("bad port event message size"); return FALSE; } if (sif->base.minor_version >= 2 && sif->event != NULL) sif->event(sin, *msg); break; default: return red_channel_client_handle_message(rcc, size, type, msg); } return TRUE; }
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; }
static gboolean create_pipeline(SpiceGstDecoder *decoder) { gchar *desc; gboolean auto_enabled; guint opt; GstAppSinkCallbacks appsink_cbs = { NULL }; GError *err = NULL; GstBus *bus; auto_enabled = (g_getenv("SPICE_GSTVIDEO_AUTO") != NULL); if (auto_enabled || !VALID_VIDEO_CODEC_TYPE(decoder->base.codec_type)) { SPICE_DEBUG("Trying %s for codec type %d %s", gst_opts[0].dec_name, decoder->base.codec_type, (auto_enabled) ? "(SPICE_GSTVIDEO_AUTO is set)" : ""); opt = 0; } else { opt = decoder->base.codec_type; } /* - We schedule the frame display ourselves so set sync=false on appsink * so the pipeline decodes them as fast as possible. This will also * minimize the risk of frames getting lost when we rebuild the * pipeline. * - Set max-bytes=0 on appsrc so it does not drop frames that may be * needed by those that follow. */ desc = g_strdup_printf("appsrc name=src is-live=true format=time max-bytes=0 block=true " "%s ! %s ! videoconvert ! appsink name=sink " "caps=video/x-raw,format=BGRx sync=false drop=false", gst_opts[opt].dec_caps, gst_opts[opt].dec_name); SPICE_DEBUG("GStreamer pipeline: %s", desc); decoder->pipeline = gst_parse_launch_full(desc, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &err); g_free(desc); if (!decoder->pipeline) { spice_warning("GStreamer error: %s", err->message); g_clear_error(&err); return FALSE; } decoder->appsrc = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(decoder->pipeline), "src")); decoder->appsink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(decoder->pipeline), "sink")); appsink_cbs.new_sample = new_sample; gst_app_sink_set_callbacks(decoder->appsink, &appsink_cbs, decoder, NULL); bus = gst_pipeline_get_bus(GST_PIPELINE(decoder->pipeline)); gst_bus_add_watch(bus, handle_pipeline_message, decoder); gst_object_unref(bus); decoder->clock = gst_pipeline_get_clock(GST_PIPELINE(decoder->pipeline)); if (gst_element_set_state(decoder->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { SPICE_DEBUG("GStreamer error: Unable to set the pipeline to the playing state."); free_pipeline(decoder); return FALSE; } return TRUE; }
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; }
/* spice_gst_decoder_queue_frame() queues the SpiceFrame for decoding and * displaying. The steps it goes through are as follows: * * 1) A SpiceGstFrame is created to keep track of SpiceFrame and some additional * metadata. The SpiceGstFrame is then pushed to the decoding_queue. * 2) frame->data, which contains the compressed frame data, is reffed and * wrapped in a GstBuffer which is pushed to the GStreamer pipeline for * decoding. * 3) As soon as the GStreamer pipeline no longer needs the compressed frame it * will call frame->unref_data() to free it. * 4) Once the decompressed frame is available the GStreamer pipeline calls * new_sample() in the GStreamer thread. * 5) new_sample() then matches the decompressed frame to a SpiceGstFrame from * the decoding queue using the GStreamer timestamp information to deal with * dropped frames. The SpiceGstFrame is popped from the decoding_queue. * 6) new_sample() then attaches the decompressed frame to the SpiceGstFrame, * pushes it to the display_queue and calls schedule_frame(). * 7) schedule_frame() then uses gstframe->frame->mm_time to arrange for * display_frame() to be called, in the main thread, at the right time for * the next frame. * 8) display_frame() pops the first SpiceGstFrame from the display_queue and * calls stream_display_frame(). * 9) display_frame() then frees the SpiceGstFrame, which frees the SpiceFrame * and decompressed frame with it. */ static gboolean spice_gst_decoder_queue_frame(VideoDecoder *video_decoder, SpiceFrame *frame, int latency) { SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder; if (frame->size == 0) { SPICE_DEBUG("got an empty frame buffer!"); frame->free(frame); return TRUE; } if (frame->mm_time < decoder->last_mm_time) { SPICE_DEBUG("new-frame-time < last-frame-time (%u < %u):" " resetting stream", frame->mm_time, decoder->last_mm_time); /* Let GStreamer deal with the frame anyway */ } decoder->last_mm_time = frame->mm_time; if (latency < 0 && decoder->base.codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG) { /* Dropping MJPEG frames has no impact on those that follow and * saves CPU so do it. */ SPICE_DEBUG("dropping a late MJPEG frame"); frame->free(frame); return TRUE; } if (decoder->pipeline == NULL) { /* An error occurred, causing the GStreamer pipeline to be freed */ spice_warning("An error occurred, stopping the video stream"); return FALSE; } /* ref() the frame data for the buffer */ frame->ref_data(frame->data_opaque); GstBuffer *buffer = gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS, frame->data, frame->size, 0, frame->size, frame->data_opaque, frame->unref_data); GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE; GST_BUFFER_DTS(buffer) = GST_CLOCK_TIME_NONE; GST_BUFFER_PTS(buffer) = gst_clock_get_time(decoder->clock) - gst_element_get_base_time(decoder->pipeline) + ((uint64_t)MAX(0, latency)) * 1000 * 1000; g_mutex_lock(&decoder->queues_mutex); g_queue_push_tail(decoder->decoding_queue, create_gst_frame(buffer, frame)); g_mutex_unlock(&decoder->queues_mutex); if (gst_app_src_push_buffer(decoder->appsrc, buffer) != GST_FLOW_OK) { SPICE_DEBUG("GStreamer error: unable to push frame of size %u", frame->size); stream_dropped_frame_on_playback(decoder->base.stream); } return TRUE; }
void red_dispatcher_client_monitors_config(VDAgentMonitorsConfig *monitors_config) { RedDispatcher *now = dispatchers; while (now) { if (!now->qxl->st->qif->client_monitors_config || !now->qxl->st->qif->client_monitors_config(now->qxl, monitors_config)) { spice_warning("spice bug: QXLInterface::client_monitors_config" " failed/missing unexpectedly\n"); } now = now->next; } }
static gboolean gstvideo_init(void) { static int success = 0; if (!success) { GError *err = NULL; if (gst_init_check(NULL, NULL, &err)) { success = 1; } else { spice_warning("Disabling GStreamer video support: %s", err->message); g_clear_error(&err); success = -1; } } return success > 0; }
static void spicevmc_connect(RedChannel *channel, RedClient *client, RedsStream *stream, int migration, int num_common_caps, uint32_t *common_caps, int num_caps, uint32_t *caps) { RedChannelClient *rcc; SpiceVmcState *state; SpiceCharDeviceInstance *sin; SpiceCharDeviceInterface *sif; state = SPICE_CONTAINEROF(channel, SpiceVmcState, channel); sin = state->chardev_sin; sif = SPICE_CONTAINEROF(sin->base.sif, SpiceCharDeviceInterface, base); if (state->rcc) { spice_printerr("channel client %d:%d (%p) already connected, refusing second connection", channel->type, channel->id, state->rcc); // TODO: notify client in advance about the in use channel using // SPICE_MSG_MAIN_CHANNEL_IN_USE (for example) reds_stream_free(stream); return; } rcc = red_channel_client_create(sizeof(RedChannelClient), channel, client, stream, FALSE, num_common_caps, common_caps, num_caps, caps); if (!rcc) { return; } state->rcc = rcc; red_channel_client_ack_zero_messages_window(rcc); if (strcmp(sin->subtype, "port") == 0) { spicevmc_port_send_init(rcc); } if (!spice_char_device_client_add(state->chardev_st, client, FALSE, 0, ~0, ~0, red_channel_client_waits_for_migrate_data(rcc))) { spice_warning("failed to add client to spicevmc"); red_channel_client_disconnect(rcc); return; } if (sif->state) { sif->state(sin, 1); } }
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_debug("Wait for client mechname"); reds_stream_async_read(stream, (uint8_t *)sasl->mechname, sasl->len, read_cb, opaque); return true; }
void cursor_channel_process_cmd(CursorChannel *cursor, RedCursorCmd *cursor_cmd, uint32_t group_id) { CursorItem *cursor_item; int cursor_show = FALSE; spice_return_if_fail(cursor); spice_return_if_fail(cursor_cmd); cursor_item = cursor_item_new(red_worker_get_qxl(cursor->common.worker), cursor_cmd, group_id); switch (cursor_cmd->type) { case QXL_CURSOR_SET: cursor->cursor_visible = cursor_cmd->u.set.visible; cursor_set_item(cursor, cursor_item); break; case QXL_CURSOR_MOVE: cursor_show = !cursor->cursor_visible; cursor->cursor_visible = TRUE; cursor->cursor_position = cursor_cmd->u.position; break; case QXL_CURSOR_HIDE: cursor->cursor_visible = FALSE; break; case QXL_CURSOR_TRAIL: cursor->cursor_trail_length = cursor_cmd->u.trail.length; cursor->cursor_trail_frequency = cursor_cmd->u.trail.frequency; break; default: spice_warning("invalid cursor command %u", cursor_cmd->type); return; } if (red_channel_is_connected(&cursor->common.base) && (cursor->mouse_mode == SPICE_MOUSE_MODE_SERVER || cursor_cmd->type != QXL_CURSOR_MOVE || cursor_show)) { red_channel_pipes_new_add(&cursor->common.base, new_cursor_pipe_item, cursor_item); } cursor_item_unref(cursor_item); }
RedsSaslError reds_sasl_handle_auth_startlen(RedsStream *stream, AsyncReadDone read_cb, void *opaque) { RedsSASL *sasl = &stream->priv->sasl; spice_debug("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; }
static int smartcard_channel_client_handle_migrate_data(RedChannelClient *rcc, uint32_t size, void *message) { SmartCardChannelClient *scc; SpiceMigrateDataHeader *header; SpiceMigrateDataSmartcard *mig_data; scc = SPICE_CONTAINEROF(rcc, SmartCardChannelClient, base); header = (SpiceMigrateDataHeader *)message; mig_data = (SpiceMigrateDataSmartcard *)(header + 1); if (size < sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataSmartcard)) { spice_error("bad message size"); return FALSE; } if (!migration_protocol_validate_header(header, SPICE_MIGRATE_DATA_SMARTCARD_MAGIC, SPICE_MIGRATE_DATA_SMARTCARD_VERSION)) { spice_error("bad header"); return FALSE; } if (!mig_data->base.connected) { /* client wasn't attached to a smartcard */ return TRUE; } if (!scc->smartcard_state) { SpiceCharDeviceInstance *char_device = smartcard_readers_get_unattached(); if (!char_device) { spice_warning("no unattached device available"); return TRUE; } else { smartcard_char_device_attach_client(char_device, scc); } } spice_debug("reader added %d partial read_size %u", mig_data->reader_added, mig_data->read_size); scc->smartcard_state->reader_added = mig_data->reader_added; smartcard_device_state_restore_partial_read(scc->smartcard_state, mig_data); return spice_char_device_state_restore(scc->smartcard_state->chardev_st, &mig_data->base); }
static gboolean handle_pipeline_message(GstBus *bus, GstMessage *msg, gpointer video_decoder) { SpiceGstDecoder *decoder = video_decoder; if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) { GError *err = NULL; gchar *debug_info = NULL; gst_message_parse_error(msg, &err, &debug_info); spice_warning("GStreamer error from element %s: %s", GST_OBJECT_NAME(msg->src), err->message); if (debug_info) { SPICE_DEBUG("debug information: %s", debug_info); g_free(debug_info); } g_clear_error(&err); /* We won't be able to process any more frame anyway */ free_pipeline(decoder); } return TRUE; }
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; }
SPICE_GNUC_VISIBLE void spice_server_port_event(SpiceCharDeviceInstance *sin, uint8_t event) { SpiceVmcState *state; if (sin->st == NULL) { spice_warning("no SpiceCharDeviceState attached to instance %p", sin); return; } state = (SpiceVmcState *)spice_char_device_state_opaque_get(sin->st); if (event == SPICE_PORT_EVENT_OPENED) { state->port_opened = TRUE; } else if (event == SPICE_PORT_EVENT_CLOSED) { state->port_opened = FALSE; } if (state->rcc == NULL) { return; } spicevmc_port_send_event(state->rcc, event); }
static void smartcard_char_device_attach_client(SpiceCharDeviceInstance *char_device, SmartCardChannelClient *scc) { SmartCardDeviceState *st = spice_char_device_state_opaque_get(char_device->st); int client_added; spice_assert(!scc->smartcard_state && !st->scc); st->scc = scc; scc->smartcard_state = st; client_added = spice_char_device_client_add(st->chardev_st, scc->base.client, FALSE, /* no flow control yet */ 0, /* send queue size */ ~0, ~0, red_channel_client_waits_for_migrate_data( &scc->base)); if (!client_added) { spice_warning("failed"); st->scc = NULL; scc->smartcard_state = NULL; red_channel_client_disconnect(&scc->base); } }
RedsSaslError reds_sasl_handle_auth_steplen(RedsStream *stream, AsyncReadDone read_cb, void *opaque) { RedsSASL *sasl = &stream->priv->sasl; spice_debug("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; } }
void red_dispatcher_async_complete(struct RedDispatcher *dispatcher, AsyncCommand *async_command) { pthread_mutex_lock(&dispatcher->async_lock); ring_remove(&async_command->link); spice_debug("%p: cookie %" PRId64, async_command, async_command->cookie); if (ring_is_empty(&dispatcher->async_commands)) { spice_debug("no more async commands"); } pthread_mutex_unlock(&dispatcher->async_lock); switch (async_command->message) { case RED_WORKER_MESSAGE_UPDATE_ASYNC: break; case RED_WORKER_MESSAGE_ADD_MEMSLOT_ASYNC: break; case RED_WORKER_MESSAGE_DESTROY_SURFACES_ASYNC: break; case RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE_ASYNC: red_dispatcher_create_primary_surface_complete(dispatcher); break; case RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE_ASYNC: red_dispatcher_destroy_primary_surface_complete(dispatcher); break; case RED_WORKER_MESSAGE_DESTROY_SURFACE_WAIT_ASYNC: break; case RED_WORKER_MESSAGE_FLUSH_SURFACES_ASYNC: break; case RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC: break; default: spice_warning("unexpected message %d", async_command->message); } dispatcher->qxl->st->qif->async_complete(dispatcher->qxl, async_command->cookie); free(async_command); }
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_debug("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_debug("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_debug("%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_debug("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; }
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_debug("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_debug("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; }