#include "trace.h"
#include "hw/qdev-core.h"
#include "sysemu/sysemu.h"
+#include "sysemu/runstate.h"
#include "qemu/error-report.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
#include "crypto/tlscredsanon.h"
#include "crypto/tlscredsx509.h"
#include "crypto/random.h"
+#include "crypto/secret_common.h"
#include "qom/object_interfaces.h"
#include "qemu/cutils.h"
+#include "qemu/help_option.h"
#include "io/dns-resolver.h"
#define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
static VncClientInfoList *qmp_query_client_list(VncDisplay *vd)
{
- VncClientInfoList *cinfo, *prev = NULL;
+ VncClientInfoList *prev = NULL;
VncState *client;
QTAILQ_FOREACH(client, &vd->clients, next) {
- cinfo = g_new0(VncClientInfoList, 1);
- cinfo->value = qmp_query_vnc_client(client);
- cinfo->next = prev;
- prev = cinfo;
+ QAPI_LIST_PREPEND(prev, qmp_query_vnc_client(client));
}
return prev;
}
int subauth,
VncServerInfo2List *prev)
{
- VncServerInfo2List *list;
VncServerInfo2 *info;
Error *err = NULL;
SocketAddress *addr;
qmp_query_auth(auth, subauth, &info->auth,
&info->vencrypt, &info->has_vencrypt);
- list = g_new0(VncServerInfo2List, 1);
- list->value = info;
- list->next = prev;
- return list;
+ QAPI_LIST_PREPEND(prev, info);
+ return prev;
}
static void qmp_query_auth(int auth, int subauth,
VncInfo2List *qmp_query_vnc_servers(Error **errp)
{
- VncInfo2List *item, *prev = NULL;
+ VncInfo2List *prev = NULL;
VncInfo2 *info;
VncDisplay *vd;
DeviceState *dev;
vd->ws_subauth, info->server);
}
- item = g_new0(VncInfo2List, 1);
- item->value = info;
- item->next = prev;
- prev = item;
+ QAPI_LIST_PREPEND(prev, info);
}
return prev;
}
+bool vnc_display_reload_certs(const char *id, Error **errp)
+{
+ VncDisplay *vd = vnc_display_find(id);
+ QCryptoTLSCredsClass *creds = NULL;
+
+ if (!vd) {
+ error_setg(errp, "Can not find vnc display");
+ return false;
+ }
+
+ if (!vd->tlscreds) {
+ error_setg(errp, "vnc tls is not enable");
+ return false;
+ }
+
+ creds = QCRYPTO_TLS_CREDS_GET_CLASS(OBJECT(vd->tlscreds));
+ if (creds->reload == NULL) {
+ error_setg(errp, "%s doesn't support to reload TLS credential",
+ object_get_typename(OBJECT(vd->tlscreds)));
+ return false;
+ }
+ if (!creds->reload(vd->tlscreds, errp)) {
+ return false;
+ }
+
+ return true;
+}
+
/* TODO
1) Get the queue working for IO.
2) there is some weirdness when using the -S option (the screen is grey
VNC_DIRTY_PIXELS_PER_BIT));
}
+static int vnc_true_width(VncDisplay *vd)
+{
+ return MIN(VNC_MAX_WIDTH, surface_width(vd->ds));
+}
+
static int vnc_height(VncDisplay *vd)
{
return MIN(VNC_MAX_HEIGHT, surface_height(vd->ds));
vnc_write_s32(vs, encoding);
}
+static void vnc_desktop_resize_ext(VncState *vs, int reject_reason)
+{
+ trace_vnc_msg_server_ext_desktop_resize(
+ vs, vs->ioc, vs->client_width, vs->client_height, reject_reason);
+
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0);
+ vnc_write_u16(vs, 1); /* number of rects */
+ vnc_framebuffer_update(vs,
+ reject_reason ? 1 : 0,
+ reject_reason,
+ vs->client_width, vs->client_height,
+ VNC_ENCODING_DESKTOP_RESIZE_EXT);
+ vnc_write_u8(vs, 1); /* number of screens */
+ vnc_write_u8(vs, 0); /* padding */
+ vnc_write_u8(vs, 0); /* padding */
+ vnc_write_u8(vs, 0); /* padding */
+ vnc_write_u32(vs, 0); /* screen id */
+ vnc_write_u16(vs, 0); /* screen x-pos */
+ vnc_write_u16(vs, 0); /* screen y-pos */
+ vnc_write_u16(vs, vs->client_width);
+ vnc_write_u16(vs, vs->client_height);
+ vnc_write_u32(vs, 0); /* screen flags */
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
static void vnc_desktop_resize(VncState *vs)
{
- if (vs->ioc == NULL || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) {
+ if (vs->ioc == NULL || (!vnc_has_feature(vs, VNC_FEATURE_RESIZE) &&
+ !vnc_has_feature(vs, VNC_FEATURE_RESIZE_EXT))) {
return;
}
- if (vs->client_width == pixman_image_get_width(vs->vd->server) &&
+ if (vs->client_width == vs->vd->true_width &&
vs->client_height == pixman_image_get_height(vs->vd->server)) {
return;
}
- assert(pixman_image_get_width(vs->vd->server) < 65536 &&
- pixman_image_get_width(vs->vd->server) >= 0);
+ assert(vs->vd->true_width < 65536 &&
+ vs->vd->true_width >= 0);
assert(pixman_image_get_height(vs->vd->server) < 65536 &&
pixman_image_get_height(vs->vd->server) >= 0);
- vs->client_width = pixman_image_get_width(vs->vd->server);
+ vs->client_width = vs->vd->true_width;
vs->client_height = pixman_image_get_height(vs->vd->server);
+
+ if (vnc_has_feature(vs, VNC_FEATURE_RESIZE_EXT)) {
+ vnc_desktop_resize_ext(vs, 0);
+ return;
+ }
+
+ trace_vnc_msg_server_desktop_resize(
+ vs, vs->ioc, vs->client_width, vs->client_height);
+
vnc_lock_output(vs);
vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
vnc_write_u8(vs, 0);
width = vnc_width(vd);
height = vnc_height(vd);
+ vd->true_width = vnc_true_width(vd);
vd->server = pixman_image_create_bits(VNC_SERVER_FB_FORMAT,
width, height,
NULL, 0);
static void vnc_dpy_switch(DisplayChangeListener *dcl,
DisplaySurface *surface)
{
- static const char placeholder_msg[] =
- "Display output is not active.";
- static DisplaySurface *placeholder;
VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
bool pageflip = vnc_check_pageflip(vd->ds, surface);
VncState *vs;
- if (surface == NULL) {
- if (placeholder == NULL) {
- placeholder = qemu_create_message_surface(640, 480, placeholder_msg);
- }
- surface = placeholder;
- }
-
vnc_abort_display_jobs(vd);
vd->ds = surface;
vd->guest.fb = pixman_image_ref(surface->image);
vd->guest.format = surface->format;
+
if (pageflip) {
+ trace_vnc_server_dpy_pageflip(vd,
+ surface_width(surface),
+ surface_height(surface),
+ surface_format(surface));
vnc_set_area_dirty(vd->guest.dirty, vd, 0, 0,
surface_width(surface),
surface_height(surface));
return;
}
+ trace_vnc_server_dpy_recreate(vd,
+ surface_width(surface),
+ surface_height(surface),
+ surface_format(surface));
/* server surface */
vnc_update_server_surface(vd);
QTAILQ_FOREACH(vs, &vd->clients, next) {
vnc_colordepth(vs);
vnc_desktop_resize(vs);
- if (vs->vd->cursor) {
- vnc_cursor_define(vs);
- }
+ vnc_cursor_define(vs);
memset(vs->dirty, 0x00, sizeof(vs->dirty));
vnc_set_area_dirty(vs->dirty, vd, 0, 0,
vnc_width(vd),
QEMUCursor *c = vs->vd->cursor;
int isize;
+ if (!vs->vd->cursor) {
+ return -1;
+ }
+
if (vnc_has_feature(vs, VNC_FEATURE_ALPHA_CURSOR)) {
vnc_lock_output(vs);
vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
assert(vs->magic == VNC_MAGIC);
switch (cmd) {
case AUD_CNOTIFY_DISABLE:
+ trace_vnc_msg_server_audio_end(vs, vs->ioc);
vnc_lock_output(vs);
vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
break;
case AUD_CNOTIFY_ENABLE:
+ trace_vnc_msg_server_audio_begin(vs, vs->ioc);
vnc_lock_output(vs);
vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
VncState *vs = opaque;
assert(vs->magic == VNC_MAGIC);
+ trace_vnc_msg_server_audio_data(vs, vs->ioc, buf, size);
vnc_lock_output(vs);
if (vs->output.offset < vs->throttle_output_offset) {
vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
} else {
vs->update = VNC_STATE_UPDATE_FORCE;
vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h);
+ if (vnc_has_feature(vs, VNC_FEATURE_RESIZE_EXT)) {
+ vnc_desktop_resize_ext(vs, 0);
+ }
}
}
vnc_flush(vs);
}
+static void send_xvp_message(VncState *vs, int code)
+{
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_XVP);
+ vnc_write_u8(vs, 0); /* pad */
+ vnc_write_u8(vs, 1); /* version */
+ vnc_write_u8(vs, code);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
{
int i;
case VNC_ENCODING_DESKTOPRESIZE:
vs->features |= VNC_FEATURE_RESIZE_MASK;
break;
+ case VNC_ENCODING_DESKTOP_RESIZE_EXT:
+ vs->features |= VNC_FEATURE_RESIZE_EXT_MASK;
+ break;
case VNC_ENCODING_POINTER_TYPE_CHANGE:
vs->features |= VNC_FEATURE_POINTER_TYPE_CHANGE_MASK;
break;
case VNC_ENCODING_LED_STATE:
vs->features |= VNC_FEATURE_LED_STATE_MASK;
break;
+ case VNC_ENCODING_XVP:
+ if (vs->vd->power_control) {
+ vs->features |= VNC_FEATURE_XVP;
+ send_xvp_message(vs, VNC_XVP_CODE_INIT);
+ }
+ break;
case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9:
vs->tight->compression = (enc & 0x0F);
break;
vnc_desktop_resize(vs);
check_pointer_type_change(&vs->mouse_mode_notifier, NULL);
vnc_led_state_change(vs);
- if (vs->vd->cursor) {
- vnc_cursor_define(vs);
- }
+ vnc_cursor_define(vs);
}
static void set_pixel_conversion(VncState *vs)
{
int i;
+ vnc_lock_output(vs);
vnc_write_u8(vs, VNC_MSG_SERVER_SET_COLOUR_MAP_ENTRIES);
vnc_write_u8(vs, 0); /* padding */
vnc_write_u16(vs, 0); /* first color */
vnc_write_u16(vs, (((i >> pf->gshift) & pf->gmax) << (16 - pf->gbits)));
vnc_write_u16(vs, (((i >> pf->bshift) & pf->bmax) << (16 - pf->bbits)));
}
+ vnc_unlock_output(vs);
}
static void set_pixel_format(VncState *vs, int bits_per_pixel,
vnc_write_u8(vs, 0);
vnc_write_u16(vs, 1); /* number of rects */
vnc_framebuffer_update(vs, 0, 0,
- pixman_image_get_width(vs->vd->server),
- pixman_image_get_height(vs->vd->server),
+ vs->client_width,
+ vs->client_height,
VNC_ENCODING_WMVi);
pixel_format_message(vs);
vnc_unlock_output(vs);
client_cut_text(vs, read_u32(data, 4), data + 8);
break;
+ case VNC_MSG_CLIENT_XVP:
+ if (!(vs->features & VNC_FEATURE_XVP)) {
+ error_report("vnc: xvp client message while disabled");
+ vnc_client_error(vs);
+ break;
+ }
+ if (len == 1) {
+ return 4;
+ }
+ if (len == 4) {
+ uint8_t version = read_u8(data, 2);
+ uint8_t action = read_u8(data, 3);
+
+ if (version != 1) {
+ error_report("vnc: xvp client message version %d != 1",
+ version);
+ vnc_client_error(vs);
+ break;
+ }
+
+ switch (action) {
+ case VNC_XVP_ACTION_SHUTDOWN:
+ qemu_system_powerdown_request();
+ break;
+ case VNC_XVP_ACTION_REBOOT:
+ send_xvp_message(vs, VNC_XVP_CODE_FAIL);
+ break;
+ case VNC_XVP_ACTION_RESET:
+ qemu_system_reset_request(SHUTDOWN_CAUSE_HOST_QMP_SYSTEM_RESET);
+ break;
+ default:
+ send_xvp_message(vs, VNC_XVP_CODE_FAIL);
+ break;
+ }
+ }
+ break;
case VNC_MSG_CLIENT_QEMU:
if (len == 1)
return 2;
switch (read_u16 (data, 2)) {
case VNC_MSG_CLIENT_QEMU_AUDIO_ENABLE:
+ trace_vnc_msg_client_audio_enable(vs, vs->ioc);
audio_add(vs);
break;
case VNC_MSG_CLIENT_QEMU_AUDIO_DISABLE:
+ trace_vnc_msg_client_audio_disable(vs, vs->ioc);
audio_del(vs);
break;
case VNC_MSG_CLIENT_QEMU_AUDIO_SET_FORMAT:
break;
}
vs->as.freq = freq;
+ trace_vnc_msg_client_audio_format(
+ vs, vs->ioc, vs->as.fmt, vs->as.nchannels, vs->as.freq);
break;
default:
VNC_DEBUG("Invalid audio message %d\n", read_u8(data, 4));
break;
}
break;
+ case VNC_MSG_CLIENT_SET_DESKTOP_SIZE:
+ {
+ size_t size;
+ uint8_t screens;
+ int w, h;
+
+ if (len < 8) {
+ return 8;
+ }
+
+ screens = read_u8(data, 6);
+ size = 8 + screens * 16;
+ if (len < size) {
+ return size;
+ }
+ w = read_u16(data, 2);
+ h = read_u16(data, 4);
+
+ trace_vnc_msg_client_set_desktop_size(vs, vs->ioc, w, h, screens);
+ if (dpy_ui_info_supported(vs->vd->dcl.con)) {
+ QemuUIInfo info;
+ memset(&info, 0, sizeof(info));
+ info.width = w;
+ info.height = h;
+ dpy_set_ui_info(vs->vd->dcl.con, &info);
+ vnc_desktop_resize_ext(vs, 4 /* Request forwarded */);
+ } else {
+ vnc_desktop_resize_ext(vs, 3 /* Invalid screen layout */);
+ }
+
+ break;
+ }
default:
VNC_DEBUG("Msg: %d\n", data[0]);
vnc_client_error(vs);
vd->auth = VNC_AUTH_INVALID;
vd->subauth = VNC_AUTH_INVALID;
if (vd->tlscreds) {
- object_unparent(OBJECT(vd->tlscreds));
+ object_unref(OBJECT(vd->tlscreds));
vd->tlscreds = NULL;
}
if (vd->tlsauthz) {
},{
.name = "password",
.type = QEMU_OPT_BOOL,
+ },{
+ .name = "password-secret",
+ .type = QEMU_OPT_STRING,
},{
.name = "reverse",
.type = QEMU_OPT_BOOL,
},{
.name = "sasl",
.type = QEMU_OPT_BOOL,
- },{
- .name = "acl",
- .type = QEMU_OPT_BOOL,
},{
.name = "tls-authz",
.type = QEMU_OPT_STRING,
},{
.name = "audiodev",
.type = QEMU_OPT_STRING,
+ },{
+ .name = "power-control",
+ .type = QEMU_OPT_BOOL,
},
{ /* end of list */ }
},
sioc = qio_channel_socket_new();
qio_channel_set_name(QIO_CHANNEL(sioc), "vnc-reverse");
if (qio_channel_socket_connect_sync(sioc, saddr[0], errp) < 0) {
+ object_unref(OBJECT(sioc));
return -1;
}
vnc_connect(vd, sioc, false, false);
bool reverse = false;
const char *credid;
bool sasl = false;
- int acl = 0;
const char *tlsauthz;
const char *saslauthz;
int lock_key_sync = 1;
int key_delay_ms;
const char *audiodev;
+ const char *passwordSecret;
if (!vd) {
error_setg(errp, "VNC display not active");
goto fail;
}
- password = qemu_opt_get_bool(opts, "password", false);
+
+ passwordSecret = qemu_opt_get(opts, "password-secret");
+ if (passwordSecret) {
+ if (qemu_opt_get(opts, "password")) {
+ error_setg(errp,
+ "'password' flag is redundant with 'password-secret'");
+ goto fail;
+ }
+ vd->password = qcrypto_secret_lookup_as_utf8(passwordSecret,
+ errp);
+ if (!vd->password) {
+ goto fail;
+ }
+ password = true;
+ } else {
+ password = qemu_opt_get_bool(opts, "password", false);
+ }
if (password) {
if (fips_get_state()) {
error_setg(errp,
goto fail;
}
}
- if (qemu_opt_get(opts, "acl")) {
- error_report("The 'acl' option to -vnc is deprecated. "
- "Please use the 'tls-authz' and 'sasl-authz' "
- "options instead");
- }
- acl = qemu_opt_get_bool(opts, "acl", false);
tlsauthz = qemu_opt_get(opts, "tls-authz");
- if (acl && tlsauthz) {
- error_setg(errp, "'acl' option is mutually exclusive with the "
- "'tls-authz' option");
- goto fail;
- }
if (tlsauthz && !vd->tlscreds) {
error_setg(errp, "'tls-authz' provided but TLS is not enabled");
goto fail;
}
saslauthz = qemu_opt_get(opts, "sasl-authz");
- if (acl && saslauthz) {
- error_setg(errp, "'acl' option is mutually exclusive with the "
- "'sasl-authz' option");
- goto fail;
- }
if (saslauthz && !sasl) {
error_setg(errp, "'sasl-authz' provided but SASL auth is not enabled");
goto fail;
vd->non_adaptive = true;
}
+ vd->power_control = qemu_opt_get_bool(opts, "power-control", false);
+
if (tlsauthz) {
vd->tlsauthzid = g_strdup(tlsauthz);
- } else if (acl) {
- if (strcmp(vd->id, "default") == 0) {
- vd->tlsauthzid = g_strdup("vnc.x509dname");
- } else {
- vd->tlsauthzid = g_strdup_printf("vnc.%s.x509dname", vd->id);
- }
- vd->tlsauthz = QAUTHZ(qauthz_list_new(vd->tlsauthzid,
- QAUTHZ_LIST_POLICY_DENY,
- &error_abort));
}
#ifdef CONFIG_VNC_SASL
if (sasl) {
if (saslauthz) {
vd->sasl.authzid = g_strdup(saslauthz);
- } else if (acl) {
- if (strcmp(vd->id, "default") == 0) {
- vd->sasl.authzid = g_strdup("vnc.username");
- } else {
- vd->sasl.authzid = g_strdup_printf("vnc.%s.username", vd->id);
- }
- vd->sasl.authz = QAUTHZ(qauthz_list_new(vd->sasl.authzid,
- QAUTHZ_LIST_POLICY_DENY,
- &error_abort));
}
}
#endif
qemu_opts_set_id(opts, id);
}
-QemuOpts *vnc_parse(const char *str, Error **errp)
+void vnc_parse(const char *str)
{
QemuOptsList *olist = qemu_find_opts("vnc");
- QemuOpts *opts = qemu_opts_parse(olist, str, true, errp);
+ QemuOpts *opts = qemu_opts_parse_noisily(olist, str, !is_help_option(str));
const char *id;
if (!opts) {
- return NULL;
+ exit(1);
}
id = qemu_opts_id(opts);
/* auto-assign id if not present */
vnc_auto_assign_id(olist, opts);
}
- return opts;
}
int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)