/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2009 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 <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <limits.h>
#include <time.h>
#include <pthread.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/bn.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#if HAVE_SASL
#include <sasl/sasl.h>
#endif
#include <spice/protocol.h>
#include <spice/vd_agent.h>
#include <spice/stats.h>
#include "common/generated_server_marshallers.h"
#include "common/ring.h"
#include "spice.h"
#include "spice-experimental.h"
#include "reds.h"
#include "agent-msg-filter.h"
#include "inputs_channel.h"
#include "main_channel.h"
#include "red_common.h"
#include "red_dispatcher.h"
#include "main_dispatcher.h"
#include "snd_worker.h"
#include "stat.h"
#include "demarshallers.h"
#include "char_device.h"
#ifdef USE_TUNNEL
#include "red_tunnel_worker.h"
#endif
#ifdef USE_SMARTCARD
#include "smartcard.h"
#endif
SpiceCoreInterface *core = NULL;
static SpiceCharDeviceInstance *vdagent = NULL;
static SpiceMigrateInstance *migration_interface = NULL;
/* Debugging only variable: allow multiple client connections to the spice
* server */
#define SPICE_DEBUG_ALLOW_MC_ENV "SPICE_DEBUG_ALLOW_MC"
#define MIGRATION_NOTIFY_SPICE_KEY "spice_mig_ext"
#define REDS_MIG_VERSION 3
#define REDS_MIG_CONTINUE 1
#define REDS_MIG_ABORT 2
#define REDS_MIG_DIFF_VERSION 3
#define REDS_TOKENS_TO_SEND 5
#define REDS_VDI_PORT_NUM_RECEIVE_BUFFS 5
static int spice_port = -1;
static int spice_secure_port = -1;
static int spice_listen_socket_fd = -1;
static char spice_addr[256];
static int spice_family = PF_UNSPEC;
static const 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 char *spice_name = NULL;
static bool spice_uuid_is_set = FALSE;
static uint8_t spice_uuid[16] = { 0, };
static int ticketing_enabled = 1; //Ticketing is enabled by default
static pthread_mutex_t *lock_cs;
static long *lock_count;
uint32_t streaming_video = STREAM_VIDEO_FILTER;
spice_image_compression_t image_compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ;
spice_wan_compression_t jpeg_state = SPICE_WAN_COMPRESSION_AUTO;
spice_wan_compression_t zlib_glz_state = SPICE_WAN_COMPRESSION_AUTO;
#ifdef USE_TUNNEL
void *red_tunnel = NULL;
#endif
int agent_mouse = TRUE;
int agent_copypaste = TRUE;
#define MIGRATE_TIMEOUT (1000 * 10) /* 10sec */
#define MM_TIMER_GRANULARITY_MS (1000 / 30)
#define MM_TIME_DELTA 400 /*ms*/
#define VDI_PORT_WRITE_RETRY_TIMEOUT 100 /*ms*/
typedef struct TicketAuthentication {
char password[SPICE_MAX_PASSWORD_LENGTH];
time_t expiration_time;
} TicketAuthentication;
static TicketAuthentication taTicket;
typedef struct TicketInfo {
RSA *rsa;
int rsa_size;
BIGNUM *bn;
SpiceLinkEncryptedTicket encrypted_ticket;
} TicketInfo;
typedef struct MonitorMode {
uint32_t x_res;
uint32_t y_res;
} MonitorMode;
typedef struct VDIReadBuf {
RingItem link;
uint32_t refs;
int len;
uint8_t data[SPICE_AGENT_MAX_DATA_SIZE];
} VDIReadBuf;
static VDIReadBuf *vdi_port_read_buf_get(void);
static VDIReadBuf *vdi_port_read_buf_ref(VDIReadBuf *buf);
static void vdi_port_read_buf_unref(VDIReadBuf *buf);
enum {
VDI_PORT_READ_STATE_READ_HADER,
VDI_PORT_READ_STATE_GET_BUFF,
VDI_PORT_READ_STATE_READ_DATA,
};
typedef struct VDIPortState {
SpiceCharDeviceState *base;
uint32_t plug_generation;
/* write to agent */
SpiceCharDeviceWriteBuffer *recv_from_client_buf;
int recv_from_client_buf_pushed;
AgentMsgFilter write_filter;
/* read from agent */
Ring read_bufs;
uint32_t read_state;
uint32_t message_recive_len;
uint8_t *recive_pos;
uint32_t recive_len;
VDIReadBuf *current_read_buf;
AgentMsgFilter read_filter;
VDIChunkHeader vdi_chunk_header;
} VDIPortState;
/* messages that are addressed to the agent and are created in the server */
typedef struct __attribute__ ((__packed__)) VDInternalBuf {
VDIChunkHeader chunk_header;
VDAgentMessage header;
union {
VDAgentMouseState mouse_state;
}
u;
} VDInternalBuf;
#ifdef RED_STATISTICS
#define REDS_MAX_STAT_NODES 100
#define REDS_STAT_SHM_SIZE (sizeof(SpiceStat) + REDS_MAX_STAT_NODES * sizeof(SpiceStatNode))
typedef struct RedsStatValue {
uint32_t value;
uint32_t min;
uint32_t max;
uint32_t average;
uint32_t count;
} RedsStatValue;
#endif
typedef struct RedsMigPendingLink {
RingItem ring_link; // list of links that belongs to the same client
SpiceLinkMess *link_msg;
RedsStream *stream;
} RedsMigPendingLink;
typedef struct RedsMigTargetClient {
RingItem link;
RedClient *client;
Ring pending_links;
} RedsMigTargetClient;
typedef struct RedsState {
int listen_socket;
int secure_listen_socket;
SpiceWatch *listen_watch;
SpiceWatch *secure_listen_watch;
VDIPortState agent_state;
int pending_mouse_event;
Ring clients;
int num_clients;
MainChannel *main_channel;
int mig_wait_connect;
int mig_wait_disconnect;
int mig_inprogress;
int expect_migrate;
Ring mig_target_clients;
int num_mig_target_clients;
RedsMigSpice *mig_spice;
int num_of_channels;
Ring channels;
int mouse_mode;
int is_client_mouse_allowed;
int dispatcher_allows_client_mouse;
MonitorMode monitor_mode;
SpiceTimer *mig_timer;
SpiceTimer *mm_timer;
SSL_CTX *ctx;
#ifdef RED_STATISTICS
char *stat_shm_name;
SpiceStat *stat;
pthread_mutex_t stat_lock;
RedsStatValue roundtrip_stat;
#endif
int peer_minor_version;
int allow_multiple_clients;
} RedsState;
static RedsState *reds = NULL;
typedef struct AsyncRead {
RedsStream *stream;
void *opaque;
uint8_t *now;
uint8_t *end;
void (*done)(void *opaque);
void (*error)(void *opaque, int err);
} AsyncRead;
typedef struct RedLinkInfo {
RedsStream *stream;
AsyncRead asyc_read;
SpiceLinkHeader link_header;
SpiceLinkMess *link_mess;
int mess_pos;
TicketInfo tiTicketing;
SpiceLinkAuthMechanism auth_mechanism;
int skip_auth;
} RedLinkInfo;
typedef struct RedSSLParameters {
char keyfile_password[256];
char certs_file[256];
char private_key_file[256];
char ca_certificate_file[256];
char dh_key_file[256];
char ciphersuite[256];
} RedSSLParameters;
typedef struct ChannelSecurityOptions ChannelSecurityOptions;
struct ChannelSecurityOptions {
uint32_t channel_id;
uint32_t options;
ChannelSecurityOptions *next;
};
static void migrate_timeout(void *opaque);
static RedsMigTargetClient* reds_mig_target_client_find(RedClient *client);
static void reds_mig_target_client_free(RedsMigTargetClient *mig_client);
static ChannelSecurityOptions *channels_security = NULL;
static int default_channel_security =
SPICE_CHANNEL_SECURITY_NONE | SPICE_CHANNEL_SECURITY_SSL;
static RedSSLParameters ssl_parameters;
static ChannelSecurityOptions *find_channel_security(int id)
{
ChannelSecurityOptions *now = channels_security;
while (now && now->channel_id != id) {
now = now->next;
}
return now;
}
static void reds_stream_channel_event(RedsStream *s, int event)
{
if (core->base.minor_version < 3 || core->channel_event == NULL)
return;
main_dispatcher_channel_event(event, s->info);
}
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->ssl, buf, size);
if (return_code < 0) {
ssl_error = SSL_get_error(s->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->ssl, buf, size);
if (return_code < 0) {
ssl_error = SSL_get_error(s->ssl, return_code);
}
return return_code;
}
static void reds_stream_remove_watch(RedsStream* s)
{
if (s->watch) {
core->watch_remove(s->watch);
s->watch = NULL;
}
}
static void reds_link_free(RedLinkInfo *link)
{
reds_stream_free(link->stream);
link->stream = NULL;
free(link->link_mess);
link->link_mess = NULL;
BN_free(link->tiTicketing.bn);
link->tiTicketing.bn = NULL;
if (link->tiTicketing.rsa) {
RSA_free(link->tiTicketing.rsa);
link->tiTicketing.rsa = NULL;
}
free(link);
}
#ifdef RED_STATISTICS
static void insert_stat_node(StatNodeRef parent, StatNodeRef ref)
{
SpiceStatNode *node = &reds->stat->nodes[ref];
uint32_t pos = INVALID_STAT_REF;
uint32_t node_index;
uint32_t *head;
SpiceStatNode *n;
node->first_child_index = INVALID_STAT_REF;
head = (parent == INVALID_STAT_REF ? &reds->stat->root_index :
&reds->stat->nodes[parent].first_child_index);
node_index = *head;
while (node_index != INVALID_STAT_REF && (n = &reds->stat->nodes[node_index]) &&
strcmp(node->name, n->name) > 0) {
pos = node_index;
node_index = n->next_sibling_index;
}
if (pos == INVALID_STAT_REF) {
node->next_sibling_index = *head;
*head = ref;
} else {
n = &reds->stat->nodes[pos];
node->next_sibling_index = n->next_sibling_index;
n->next_sibling_index = ref;
}
}
StatNodeRef stat_add_node(StatNodeRef parent, const char *name, int visible)
{
StatNodeRef ref;
SpiceStatNode *node;
spice_assert(name && strlen(name) > 0);
if (strlen(name) >= sizeof(node->name)) {
return INVALID_STAT_REF;
}
pthread_mutex_lock(&reds->stat_lock);
ref = (parent == INVALID_STAT_REF ? reds->stat->root_index :
reds->stat->nodes[parent].first_child_index);
while (ref != INVALID_STAT_REF) {
node = &reds->stat->nodes[ref];
if (strcmp(name, node->name)) {
ref = node->next_sibling_index;
} else {
pthread_mutex_unlock(&reds->stat_lock);
return ref;
}
}
if (reds->stat->num_of_nodes >= REDS_MAX_STAT_NODES || reds->stat == NULL) {
pthread_mutex_unlock(&reds->stat_lock);
return INVALID_STAT_REF;
}
reds->stat->generation++;
reds->stat->num_of_nodes++;
for (ref = 0; ref <= REDS_MAX_STAT_NODES; ref++) {
node = &reds->stat->nodes[ref];
if (!(node->flags & SPICE_STAT_NODE_FLAG_ENABLED)) {
break;
}
}
spice_assert(!(node->flags & SPICE_STAT_NODE_FLAG_ENABLED));
node->value = 0;
node->flags = SPICE_STAT_NODE_FLAG_ENABLED | (visible ? SPICE_STAT_NODE_FLAG_VISIBLE : 0);
strncpy(node->name, name, sizeof(node->name));
insert_stat_node(parent, ref);
pthread_mutex_unlock(&reds->stat_lock);
return ref;
}
static void stat_remove(SpiceStatNode *node)
{
pthread_mutex_lock(&reds->stat_lock);
node->flags &= ~SPICE_STAT_NODE_FLAG_ENABLED;
reds->stat->generation++;
reds->stat->num_of_nodes--;
pthread_mutex_unlock(&reds->stat_lock);
}
void stat_remove_node(StatNodeRef ref)
{
stat_remove(&reds->stat->nodes[ref]);
}
uint64_t *stat_add_counter(StatNodeRef parent, const char *name, int visible)
{
StatNodeRef ref = stat_add_node(parent, name, visible);
SpiceStatNode *node;
if (ref == INVALID_STAT_REF) {
return NULL;
}
node = &reds->stat->nodes[ref];
node->flags |= SPICE_STAT_NODE_FLAG_VALUE;
return &node->value;
}
void stat_remove_counter(uint64_t *counter)
{
stat_remove((SpiceStatNode *)(counter - offsetof(SpiceStatNode, value)));
}
void reds_update_stat_value(uint32_t value)
{
RedsStatValue *stat_value = &reds->roundtrip_stat;
stat_value->value = value;
stat_value->min = (stat_value->count ? MIN(stat_value->min, value) : value);
stat_value->max = MAX(stat_value->max, value);
stat_value->average = (stat_value->average * stat_value->count + value) /
(stat_value->count + 1);
stat_value->count++;
}
#endif
void reds_register_channel(RedChannel *channel)
{
spice_assert(reds);
ring_add(&reds->channels, &channel->link);
reds->num_of_channels++;
}
void reds_unregister_channel(RedChannel *channel)
{
if (ring_item_is_linked(&channel->link)) {
ring_remove(&channel->link);
reds->num_of_channels--;
} else {
spice_printerr("not found");
}
}
static RedChannel *reds_find_channel(uint32_t type, uint32_t id)
{
RingItem *now;
RING_FOREACH(now, &reds->channels) {
RedChannel *channel = SPICE_CONTAINEROF(now, RedChannel, link);
if (channel->type == type && channel->id == id) {
return channel;
}
}
return NULL;
}
static void reds_mig_cleanup(void)
{
if (reds->mig_inprogress) {
if (reds->mig_wait_connect) {
SpiceMigrateInterface *sif;
spice_assert(migration_interface);
sif = SPICE_CONTAINEROF(migration_interface->base.sif, SpiceMigrateInterface, base);
sif->migrate_connect_complete(migration_interface);
}
reds->mig_inprogress = FALSE;
reds->mig_wait_connect = FALSE;
reds->mig_wait_disconnect = FALSE;
core->timer_cancel(reds->mig_timer);
}
}
static void reds_reset_vdp(void)
{
VDIPortState *state = &reds->agent_state;
SpiceCharDeviceInterface *sif;
state->read_state = VDI_PORT_READ_STATE_READ_HADER;
state->recive_pos = (uint8_t *)&state->vdi_chunk_header;
state->recive_len = sizeof(state->vdi_chunk_header);
state->message_recive_len = 0;
if (state->current_read_buf) {
vdi_port_read_buf_unref(state->current_read_buf);
state->current_read_buf = NULL;
}
/* Reset read filter to start with clean state when the agent reconnects */
agent_msg_filter_init(&state->read_filter, agent_copypaste, TRUE);
/* Throw away pending chunks from the current (if any) and future
messages written by the client */
state->write_filter.result = AGENT_MSG_FILTER_DISCARD;
state->write_filter.discard_all = TRUE;
/* reseting and not destroying the state as a workaround for a bad
* tokens management in the vdagent protocol:
* The client tokens' are set only once, when the main channel is initialized.
* Instead, it would have been more appropriate to reset them upon AGEN_CONNECT.
* The client tokens are tracked as part of the SpiceCharDeviceClientState. Thus,
* in order to be backwartd compatible with the client, we need to track the tokens
* event when the agent is detached. We don't destroy the the char_device state, and
* instead we just reset it.
* In addition, there is a misshandling of AGENT_TOKENS message in spice-gtk: it
* overrides the amount of tokens, instead of adding the given amount.
*
* TODO: change AGENT_CONNECT msg to contain tokens count.
*/
|