From a19cbfb346425cc760ed19b4e746417df636b761 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 27 Apr 2010 11:50:11 +0200 Subject: spice: add qxl device qxl is a paravirtual graphics card. The qxl device is the bridge between the guest and the spice server (aka libspice-server). The spice server will send the rendering commands to the spice client, which will actually render them. The spice server is also able to render locally, which is done in case the guest wants read something from video memory. Local rendering is also used to support display over vnc and sdl. qxl is activated using "-vga qxl". qxl supports multihead, additional cards can be added via '-device qxl". [ v2: add copyright to files ] [ v2: use qemu-common.h for standard includes ] [ v2: create separate qxl-vga device for primary ] Signed-off-by: Gerd Hoffmann --- ui/spice-core.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'ui') diff --git a/ui/spice-core.c b/ui/spice-core.c index d13bdc2..b7fa031 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -370,6 +370,21 @@ void qemu_spice_init(void) int qemu_spice_add_interface(SpiceBaseInstance *sin) { + if (!spice_server) { + if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) { + fprintf(stderr, "Oops: spice configured but not active\n"); + exit(1); + } + /* + * Create a spice server instance. + * It does *not* listen on the network. + * It handles QXL local rendering only. + * + * With a command line like '-vnc :0 -vga qxl' you'll end up here. + */ + spice_server = spice_server_new(); + spice_server_init(spice_server, &core_interface); + } return spice_server_add_interface(spice_server, sin); } -- cgit v1.1 From 6f8c63fbd7edc0b41c09f8f8e2d41a3a65464a43 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Mon, 11 Oct 2010 18:03:51 +0200 Subject: spice: connection events. This patch adds support for connection events to spice. The events are quite simliar to the vnc events. Unlike vnc spice uses multiple tcp channels though. qemu will report every single tcp connection (aka spice channel). If you want track spice sessions only you can filter for the main channel (channel-type == 1). Signed-off-by: Gerd Hoffmann --- ui/spice-core.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'ui') diff --git a/ui/spice-core.c b/ui/spice-core.c index b7fa031..93461c6 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -18,16 +18,24 @@ #include #include +#include + #include "qemu-common.h" #include "qemu-spice.h" #include "qemu-timer.h" #include "qemu-queue.h" #include "qemu-x509.h" +#include "qemu_socket.h" +#include "qint.h" +#include "qbool.h" +#include "qstring.h" +#include "qjson.h" #include "monitor.h" /* core bits */ static SpiceServer *spice_server; +static const char *auth = "spice"; int using_spice = 0; struct SpiceTimer { @@ -121,6 +129,68 @@ static void watch_remove(SpiceWatch *watch) qemu_free(watch); } +#if SPICE_INTERFACE_CORE_MINOR >= 3 + +static void add_addr_info(QDict *dict, struct sockaddr *addr, int len) +{ + char host[NI_MAXHOST], port[NI_MAXSERV]; + const char *family; + + getnameinfo(addr, len, host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST | NI_NUMERICSERV); + family = inet_strfamily(addr->sa_family); + + qdict_put(dict, "host", qstring_from_str(host)); + qdict_put(dict, "port", qstring_from_str(port)); + qdict_put(dict, "family", qstring_from_str(family)); +} + +static void add_channel_info(QDict *dict, SpiceChannelEventInfo *info) +{ + int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS; + + qdict_put(dict, "connection-id", qint_from_int(info->connection_id)); + qdict_put(dict, "channel-type", qint_from_int(info->type)); + qdict_put(dict, "channel-id", qint_from_int(info->id)); + qdict_put(dict, "tls", qbool_from_int(tls)); +} + +static void channel_event(int event, SpiceChannelEventInfo *info) +{ + static const int qevent[] = { + [ SPICE_CHANNEL_EVENT_CONNECTED ] = QEVENT_SPICE_CONNECTED, + [ SPICE_CHANNEL_EVENT_INITIALIZED ] = QEVENT_SPICE_INITIALIZED, + [ SPICE_CHANNEL_EVENT_DISCONNECTED ] = QEVENT_SPICE_DISCONNECTED, + }; + QDict *server, *client; + QObject *data; + + client = qdict_new(); + add_addr_info(client, &info->paddr, info->plen); + + server = qdict_new(); + add_addr_info(server, &info->laddr, info->llen); + + if (event == SPICE_CHANNEL_EVENT_INITIALIZED) { + qdict_put(server, "auth", qstring_from_str(auth)); + add_channel_info(client, info); + } + + data = qobject_from_jsonf("{ 'client': %p, 'server': %p }", + QOBJECT(client), QOBJECT(server)); + monitor_protocol_event(qevent[event], data); + qobject_decref(data); +} + +#else /* SPICE_INTERFACE_CORE_MINOR >= 3 */ + +static QList *channel_list_get(void) +{ + return NULL; +} + +#endif /* SPICE_INTERFACE_CORE_MINOR >= 3 */ + static SpiceCoreInterface core_interface = { .base.type = SPICE_INTERFACE_CORE, .base.description = "qemu core services", @@ -135,6 +205,10 @@ static SpiceCoreInterface core_interface = { .watch_add = watch_add, .watch_update_mask = watch_update_mask, .watch_remove = watch_remove, + +#if SPICE_INTERFACE_CORE_MINOR >= 3 + .channel_event = channel_event, +#endif }; /* config string parsing */ @@ -316,6 +390,7 @@ void qemu_spice_init(void) spice_server_set_ticket(spice_server, password, 0, 0, 0); } if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) { + auth = "none"; spice_server_set_noauth(spice_server); } -- cgit v1.1 From cb42a870c3f5b38911b1428cb785dd702bc47d0f Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 30 Nov 2010 11:02:51 +0100 Subject: spice: add qmp 'query-spice' and hmp 'info spice' commands. The patch adds a 'query-spice' monitor command which returns informations about the spice server configuration and also a list of channel connections. Signed-off-by: Gerd Hoffmann --- ui/qemu-spice.h | 3 ++ ui/spice-core.c | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) (limited to 'ui') diff --git a/ui/qemu-spice.h b/ui/qemu-spice.h index 0e3ad9b..8b23ac9 100644 --- a/ui/qemu-spice.h +++ b/ui/qemu-spice.h @@ -33,6 +33,9 @@ void qemu_spice_audio_init(void); void qemu_spice_display_init(DisplayState *ds); int qemu_spice_add_interface(SpiceBaseInstance *sin); +void do_info_spice_print(Monitor *mon, const QObject *data); +void do_info_spice(Monitor *mon, QObject **ret_data); + #else /* CONFIG_SPICE */ #define using_spice 0 diff --git a/ui/spice-core.c b/ui/spice-core.c index 93461c6..d29d203 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -131,6 +131,36 @@ static void watch_remove(SpiceWatch *watch) #if SPICE_INTERFACE_CORE_MINOR >= 3 +typedef struct ChannelList ChannelList; +struct ChannelList { + SpiceChannelEventInfo *info; + QTAILQ_ENTRY(ChannelList) link; +}; +static QTAILQ_HEAD(, ChannelList) channel_list = QTAILQ_HEAD_INITIALIZER(channel_list); + +static void channel_list_add(SpiceChannelEventInfo *info) +{ + ChannelList *item; + + item = qemu_mallocz(sizeof(*item)); + item->info = info; + QTAILQ_INSERT_TAIL(&channel_list, item, link); +} + +static void channel_list_del(SpiceChannelEventInfo *info) +{ + ChannelList *item; + + QTAILQ_FOREACH(item, &channel_list, link) { + if (item->info != info) { + continue; + } + QTAILQ_REMOVE(&channel_list, item, link); + qemu_free(item); + return; + } +} + static void add_addr_info(QDict *dict, struct sockaddr *addr, int len) { char host[NI_MAXHOST], port[NI_MAXSERV]; @@ -155,6 +185,22 @@ static void add_channel_info(QDict *dict, SpiceChannelEventInfo *info) qdict_put(dict, "tls", qbool_from_int(tls)); } +static QList *channel_list_get(void) +{ + ChannelList *item; + QList *list; + QDict *dict; + + list = qlist_new(); + QTAILQ_FOREACH(item, &channel_list, link) { + dict = qdict_new(); + add_addr_info(dict, &item->info->paddr, item->info->plen); + add_channel_info(dict, item->info); + qlist_append(list, dict); + } + return list; +} + static void channel_event(int event, SpiceChannelEventInfo *info) { static const int qevent[] = { @@ -174,6 +220,10 @@ static void channel_event(int event, SpiceChannelEventInfo *info) if (event == SPICE_CHANNEL_EVENT_INITIALIZED) { qdict_put(server, "auth", qstring_from_str(auth)); add_channel_info(client, info); + channel_list_add(info); + } + if (event == SPICE_CHANNEL_EVENT_DISCONNECTED) { + channel_list_del(info); } data = qobject_from_jsonf("{ 'client': %p, 'server': %p }", @@ -278,6 +328,92 @@ static const char *wan_compression_names[] = { /* functions for the rest of qemu */ +static void info_spice_iter(QObject *obj, void *opaque) +{ + QDict *client; + Monitor *mon = opaque; + + client = qobject_to_qdict(obj); + monitor_printf(mon, "Channel:\n"); + monitor_printf(mon, " address: %s:%s%s\n", + qdict_get_str(client, "host"), + qdict_get_str(client, "port"), + qdict_get_bool(client, "tls") ? " [tls]" : ""); + monitor_printf(mon, " session: %" PRId64 "\n", + qdict_get_int(client, "connection-id")); + monitor_printf(mon, " channel: %d:%d\n", + (int)qdict_get_int(client, "channel-type"), + (int)qdict_get_int(client, "channel-id")); +} + +void do_info_spice_print(Monitor *mon, const QObject *data) +{ + QDict *server; + QList *channels; + const char *host; + int port; + + server = qobject_to_qdict(data); + if (qdict_get_bool(server, "enabled") == 0) { + monitor_printf(mon, "Server: disabled\n"); + return; + } + + monitor_printf(mon, "Server:\n"); + host = qdict_get_str(server, "host"); + port = qdict_get_try_int(server, "port", -1); + if (port != -1) { + monitor_printf(mon, " address: %s:%d\n", host, port); + } + port = qdict_get_try_int(server, "tls-port", -1); + if (port != -1) { + monitor_printf(mon, " address: %s:%d [tls]\n", host, port); + } + monitor_printf(mon, " auth: %s\n", qdict_get_str(server, "auth")); + + channels = qdict_get_qlist(server, "channels"); + if (qlist_empty(channels)) { + monitor_printf(mon, "Channels: none\n"); + } else { + qlist_iter(channels, info_spice_iter, mon); + } +} + +void do_info_spice(Monitor *mon, QObject **ret_data) +{ + QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); + QDict *server; + QList *clist; + const char *addr; + int port, tls_port; + + if (!spice_server) { + *ret_data = qobject_from_jsonf("{ 'enabled': false }"); + return; + } + + addr = qemu_opt_get(opts, "addr"); + port = qemu_opt_get_number(opts, "port", 0); + tls_port = qemu_opt_get_number(opts, "tls-port", 0); + clist = channel_list_get(); + + server = qdict_new(); + qdict_put(server, "enabled", qbool_from_int(true)); + qdict_put(server, "auth", qstring_from_str(auth)); + qdict_put(server, "host", qstring_from_str(addr ? addr : "0.0.0.0")); + if (port) { + qdict_put(server, "port", qint_from_int(port)); + } + if (tls_port) { + qdict_put(server, "tls-port", qint_from_int(tls_port)); + } + if (clist) { + qdict_put(server, "channels", clist); + } + + *ret_data = QOBJECT(server); +} + static int add_channel(const char *name, const char *value, void *opaque) { int security = 0; -- cgit v1.1 From 6bffdf0f83263bad1dd2187c533758d7cb6f5bcf Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 7 Oct 2010 11:50:24 +0200 Subject: vnc: auth reject cleanup protocol_client_auth_vnc() has two places where the auth can fail, with identical code sending the reject message to the client. Move the common code to the end of the function and make both error paths jump there. No functional change. Signed-off-by: Gerd Hoffmann --- ui/vnc.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) (limited to 'ui') diff --git a/ui/vnc.c b/ui/vnc.c index 864342e..da70757 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -2085,15 +2085,7 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len) if (!vs->vd->password || !vs->vd->password[0]) { VNC_DEBUG("No password configured on server"); - vnc_write_u32(vs, 1); /* Reject auth */ - if (vs->minor >= 8) { - static const char err[] = "Authentication failed"; - vnc_write_u32(vs, sizeof(err)); - vnc_write(vs, err, sizeof(err)); - } - vnc_flush(vs); - vnc_client_error(vs); - return 0; + goto reject; } memcpy(response, vs->challenge, VNC_AUTH_CHALLENGE_SIZE); @@ -2109,14 +2101,7 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len) /* Compare expected vs actual challenge response */ if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) { VNC_DEBUG("Client challenge reponse did not match\n"); - vnc_write_u32(vs, 1); /* Reject auth */ - if (vs->minor >= 8) { - static const char err[] = "Authentication failed"; - vnc_write_u32(vs, sizeof(err)); - vnc_write(vs, err, sizeof(err)); - } - vnc_flush(vs); - vnc_client_error(vs); + goto reject; } else { VNC_DEBUG("Accepting VNC challenge response\n"); vnc_write_u32(vs, 0); /* Accept auth */ @@ -2125,6 +2110,17 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len) start_client_init(vs); } return 0; + +reject: + vnc_write_u32(vs, 1); /* Reject auth */ + if (vs->minor >= 8) { + static const char err[] = "Authentication failed"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_flush(vs); + vnc_client_error(vs); + return 0; } void start_auth_vnc(VncState *vs) -- cgit v1.1 From 3c9405a0f7d76602415b3cbe8d52d7714b6ce5af Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 7 Oct 2010 11:50:45 +0200 Subject: vnc: support password expire This patch adds support for expiring passwords to vnc. It adds a new vnc_display_pw_expire() function which specifies the time when the password will expire. Signed-off-by: Gerd Hoffmann --- ui/vnc.c | 14 ++++++++++++++ ui/vnc.h | 1 + 2 files changed, 15 insertions(+) (limited to 'ui') diff --git a/ui/vnc.c b/ui/vnc.c index da70757..495d6d6 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -2082,11 +2082,16 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len) unsigned char response[VNC_AUTH_CHALLENGE_SIZE]; int i, j, pwlen; unsigned char key[8]; + time_t now = time(NULL); if (!vs->vd->password || !vs->vd->password[0]) { VNC_DEBUG("No password configured on server"); goto reject; } + if (vs->vd->expires < now) { + VNC_DEBUG("Password is expired"); + goto reject; + } memcpy(response, vs->challenge, VNC_AUTH_CHALLENGE_SIZE); @@ -2432,6 +2437,7 @@ void vnc_display_init(DisplayState *ds) vs->ds = ds; QTAILQ_INIT(&vs->clients); + vs->expires = TIME_MAX; if (keyboard_layout) vs->kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout); @@ -2503,6 +2509,14 @@ int vnc_display_password(DisplayState *ds, const char *password) return 0; } +int vnc_display_pw_expire(DisplayState *ds, time_t expires) +{ + VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; + + vs->expires = expires; + return 0; +} + char *vnc_display_local_addr(DisplayState *ds) { VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; diff --git a/ui/vnc.h b/ui/vnc.h index 9619b24..4f895be 100644 --- a/ui/vnc.h +++ b/ui/vnc.h @@ -120,6 +120,7 @@ struct VncDisplay char *display; char *password; + time_t expires; int auth; bool lossy; #ifdef CONFIG_VNC_TLS -- cgit v1.1 From 7572150c189c6553c2448334116ab717680de66d Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 7 Oct 2010 12:22:54 +0200 Subject: vnc/spice: add set_passwd monitor command. This patch adds new set_password and expire_password monitor commands which allows to change and expire the password for spice and vnc connections. See the doc update patch chunk for details. Signed-off-by: Gerd Hoffmann --- ui/qemu-spice.h | 5 +++++ ui/spice-core.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) (limited to 'ui') diff --git a/ui/qemu-spice.h b/ui/qemu-spice.h index 8b23ac9..48239c3 100644 --- a/ui/qemu-spice.h +++ b/ui/qemu-spice.h @@ -32,6 +32,9 @@ void qemu_spice_input_init(void); void qemu_spice_audio_init(void); void qemu_spice_display_init(DisplayState *ds); int qemu_spice_add_interface(SpiceBaseInstance *sin); +int qemu_spice_set_passwd(const char *passwd, + bool fail_if_connected, bool disconnect_if_connected); +int qemu_spice_set_pw_expire(time_t expires); void do_info_spice_print(Monitor *mon, const QObject *data); void do_info_spice(Monitor *mon, QObject **ret_data); @@ -39,6 +42,8 @@ void do_info_spice(Monitor *mon, QObject **ret_data); #else /* CONFIG_SPICE */ #define using_spice 0 +#define qemu_spice_set_passwd(_p, _f1, _f2) (-1) +#define qemu_spice_set_pw_expire(_e) (-1) #endif /* CONFIG_SPICE */ diff --git a/ui/spice-core.c b/ui/spice-core.c index d29d203..27a1ced 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -36,6 +36,8 @@ static SpiceServer *spice_server; static const char *auth = "spice"; +static char *auth_passwd; +static time_t auth_expires = TIME_MAX; int using_spice = 0; struct SpiceTimer { @@ -599,6 +601,39 @@ int qemu_spice_add_interface(SpiceBaseInstance *sin) return spice_server_add_interface(spice_server, sin); } +static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn) +{ + time_t lifetime, now = time(NULL); + char *passwd; + + if (now < auth_expires) { + passwd = auth_passwd; + lifetime = (auth_expires - now); + if (lifetime > INT_MAX) { + lifetime = INT_MAX; + } + } else { + passwd = NULL; + lifetime = 1; + } + return spice_server_set_ticket(spice_server, passwd, lifetime, + fail_if_conn, disconnect_if_conn); +} + +int qemu_spice_set_passwd(const char *passwd, + bool fail_if_conn, bool disconnect_if_conn) +{ + free(auth_passwd); + auth_passwd = strdup(passwd); + return qemu_spice_set_ticket(fail_if_conn, disconnect_if_conn); +} + +int qemu_spice_set_pw_expire(time_t expires) +{ + auth_expires = expires; + return qemu_spice_set_ticket(false, false); +} + static void spice_register_config(void) { qemu_add_opts(&qemu_spice_opts); -- cgit v1.1