#include "acl.h"
#include "qemu-objects.h"
#include "qmp-commands.h"
+#include "osdep.h"
#define VNC_REFRESH_INTERVAL_BASE 30
#define VNC_REFRESH_INTERVAL_INC 50
static DisplayChangeListener *dcl;
static int vnc_cursor_define(VncState *vs);
+static void vnc_release_modifiers(VncState *vs);
+
+static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
+{
+#ifdef _VNC_DEBUG
+ static const char *mn[] = {
+ [0] = "undefined",
+ [VNC_SHARE_MODE_CONNECTING] = "connecting",
+ [VNC_SHARE_MODE_SHARED] = "shared",
+ [VNC_SHARE_MODE_EXCLUSIVE] = "exclusive",
+ [VNC_SHARE_MODE_DISCONNECTED] = "disconnected",
+ };
+ fprintf(stderr, "%s/%d: %s -> %s\n", __func__,
+ vs->csock, mn[vs->share_mode], mn[mode]);
+#endif
+
+ if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) {
+ vs->vd->num_exclusive--;
+ }
+ vs->share_mode = mode;
+ if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) {
+ vs->vd->num_exclusive++;
+ }
+}
static char *addr_to_string(const char *format,
struct sockaddr_storage *sa,
}
}
+ if (vnc_display->lsock == -1) {
+ return info;
+ }
+
if (getsockname(vnc_display->lsock, (struct sockaddr *)&sa,
&salen) == -1) {
error_set(errp, QERR_UNDEFINED_ERROR);
vnc_flush(vs);
}
-#ifdef CONFIG_VNC_THREAD
static void vnc_abort_display_jobs(VncDisplay *vd)
{
VncState *vs;
vnc_unlock_output(vs);
}
}
-#else
-static void vnc_abort_display_jobs(VncDisplay *vd)
-{
-}
-#endif
static void vnc_dpy_resize(DisplayState *ds)
{
return h;
}
-#ifdef CONFIG_VNC_THREAD
static int vnc_update_client_sync(VncState *vs, int has_dirty)
{
int ret = vnc_update_client(vs, has_dirty);
vnc_jobs_join(vs);
return ret;
}
-#else
-static int vnc_update_client_sync(VncState *vs, int has_dirty)
-{
- return vnc_update_client(vs, has_dirty);
-}
-#endif
static int vnc_update_client(VncState *vs, int has_dirty)
{
{
if (vs->csock == -1)
return;
+ vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL);
closesocket(vs->csock);
vs->csock = -1;
vnc_sasl_client_cleanup(vs);
#endif /* CONFIG_VNC_SASL */
audio_del(vs);
+ vnc_release_modifiers(vs);
QTAILQ_REMOVE(&vs->vd->clients, vs, next);
qemu_remove_led_event_handler(vs->led);
vnc_unlock_output(vs);
-#ifdef CONFIG_VNC_THREAD
qemu_mutex_destroy(&vs->output_mutex);
-#endif
+ qemu_bh_delete(vs->bh);
+ buffer_free(&vs->jobs_buffer);
+
for (i = 0; i < VNC_STAT_ROWS; ++i) {
g_free(vs->lossy_rect[i]);
}
return ret;
}
+static void vnc_jobs_bh(void *opaque)
+{
+ VncState *vs = opaque;
+
+ vnc_jobs_consume_buffer(vs);
+}
/*
* First function called whenever there is more data to be read from
else
kbd_put_keycode(keycode | SCANCODE_UP);
} else {
+ bool numlock = vs->modifiers_state[0x45];
+ bool control = (vs->modifiers_state[0x1d] ||
+ vs->modifiers_state[0x9d]);
/* QEMU console emulation */
if (down) {
- int numlock = vs->modifiers_state[0x45];
switch (keycode) {
case 0x2a: /* Left Shift */
case 0x36: /* Right Shift */
break;
default:
- kbd_put_keysym(sym);
+ if (control) {
+ kbd_put_keysym(sym & 0x1f);
+ } else {
+ kbd_put_keysym(sym);
+ }
break;
}
}
}
}
+static void vnc_release_modifiers(VncState *vs)
+{
+ static const int keycodes[] = {
+ /* shift, control, alt keys, both left & right */
+ 0x2a, 0x36, 0x1d, 0x9d, 0x38, 0xb8,
+ };
+ int i, keycode;
+
+ if (!is_graphic_console()) {
+ return;
+ }
+ for (i = 0; i < ARRAY_SIZE(keycodes); i++) {
+ keycode = keycodes[i];
+ if (!vs->modifiers_state[keycode]) {
+ continue;
+ }
+ if (keycode & SCANCODE_GREY) {
+ kbd_put_keycode(SCANCODE_EMUL0);
+ }
+ kbd_put_keycode(keycode | SCANCODE_UP);
+ }
+}
+
static void key_event(VncState *vs, int down, uint32_t sym)
{
int keycode;
vs->features |= VNC_FEATURE_TIGHT_MASK;
vs->vnc_encoding = enc;
break;
+#ifdef CONFIG_VNC_PNG
case VNC_ENCODING_TIGHT_PNG:
vs->features |= VNC_FEATURE_TIGHT_PNG_MASK;
vs->vnc_encoding = enc;
break;
+#endif
case VNC_ENCODING_ZLIB:
vs->features |= VNC_FEATURE_ZLIB_MASK;
vs->vnc_encoding = enc;
static void vnc_dpy_setdata(DisplayState *ds)
{
- /* We don't have to do anything */
+ VncDisplay *vd = ds->opaque;
+
+ *(vd->guest.ds) = *(ds->surface);
+ vnc_dpy_update(ds, 0, 0, ds_get_width(ds), ds_get_height(ds));
}
static void vnc_colordepth(VncState *vs)
static int protocol_client_init(VncState *vs, uint8_t *data, size_t len)
{
char buf[1024];
+ VncShareMode mode;
int size;
+ mode = data[0] ? VNC_SHARE_MODE_SHARED : VNC_SHARE_MODE_EXCLUSIVE;
+ switch (vs->vd->share_policy) {
+ case VNC_SHARE_POLICY_IGNORE:
+ /*
+ * Ignore the shared flag. Nothing to do here.
+ *
+ * Doesn't conform to the rfb spec but is traditional qemu
+ * behavior, thus left here as option for compatibility
+ * reasons.
+ */
+ break;
+ case VNC_SHARE_POLICY_ALLOW_EXCLUSIVE:
+ /*
+ * Policy: Allow clients ask for exclusive access.
+ *
+ * Implementation: When a client asks for exclusive access,
+ * disconnect all others. Shared connects are allowed as long
+ * as no exclusive connection exists.
+ *
+ * This is how the rfb spec suggests to handle the shared flag.
+ */
+ if (mode == VNC_SHARE_MODE_EXCLUSIVE) {
+ VncState *client;
+ QTAILQ_FOREACH(client, &vs->vd->clients, next) {
+ if (vs == client) {
+ continue;
+ }
+ if (client->share_mode != VNC_SHARE_MODE_EXCLUSIVE &&
+ client->share_mode != VNC_SHARE_MODE_SHARED) {
+ continue;
+ }
+ vnc_disconnect_start(client);
+ }
+ }
+ if (mode == VNC_SHARE_MODE_SHARED) {
+ if (vs->vd->num_exclusive > 0) {
+ vnc_disconnect_start(vs);
+ return 0;
+ }
+ }
+ break;
+ case VNC_SHARE_POLICY_FORCE_SHARED:
+ /*
+ * Policy: Shared connects only.
+ * Implementation: Disallow clients asking for exclusive access.
+ *
+ * Useful for shared desktop sessions where you don't want
+ * someone forgetting to say -shared when running the vnc
+ * client disconnect everybody else.
+ */
+ if (mode == VNC_SHARE_MODE_EXCLUSIVE) {
+ vnc_disconnect_start(vs);
+ return 0;
+ }
+ break;
+ }
+ vnc_set_share_mode(vs, mode);
+
vs->client_width = ds_get_width(vs->ds);
vs->client_height = ds_get_height(vs->ds);
vnc_write_u16(vs, vs->client_width);
* Update server dirty map.
*/
cmp_bytes = 16 * ds_get_bytes_per_pixel(vd->ds);
+ if (cmp_bytes > vd->ds->surface->linesize) {
+ cmp_bytes = vd->ds->surface->linesize;
+ }
guest_row = vd->guest.ds->data;
server_row = vd->server->data;
for (y = 0; y < vd->guest.ds->height; y++) {
guest_ptr = guest_row;
server_ptr = server_row;
- for (x = 0; x < vd->guest.ds->width;
+ for (x = 0; x + 15 < vd->guest.ds->width;
x += 16, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) {
if (!test_and_clear_bit((x / 16), vd->guest.dirty[y]))
continue;
vnc_client_cache_addr(vs);
vnc_qmp_event(vs, QEVENT_VNC_CONNECTED);
+ vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING);
vs->vd = vd;
vs->ds = vd->ds;
vs->as.fmt = AUD_FMT_S16;
vs->as.endianness = 0;
-#ifdef CONFIG_VNC_THREAD
qemu_mutex_init(&vs->output_mutex);
-#endif
+ vs->bh = qemu_bh_new(vnc_jobs_bh, vs);
QTAILQ_INSERT_HEAD(&vd->clients, vs, next);
if (!vs->kbd_layout)
exit(1);
-#ifdef CONFIG_VNC_THREAD
qemu_mutex_init(&vs->mutex);
vnc_start_worker_thread();
-#endif
dcl->dpy_copy = vnc_dpy_copy;
dcl->dpy_update = vnc_dpy_update;
}
vs->password = NULL;
- vs->auth = VNC_AUTH_VNC;
+ if (vs->auth == VNC_AUTH_NONE) {
+ vs->auth = VNC_AUTH_VNC;
+ }
return 0;
}
int vnc_display_password(DisplayState *ds, const char *password)
{
- int ret = 0;
VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display;
if (!vs) {
- ret = -EINVAL;
- goto out;
+ return -EINVAL;
}
if (!password) {
/* This is not the intention of this interface but err on the side
of being safe */
- ret = vnc_display_disable_login(ds);
- goto out;
+ return vnc_display_disable_login(ds);
}
if (vs->password) {
vs->password = NULL;
}
vs->password = g_strdup(password);
- vs->auth = VNC_AUTH_VNC;
-out:
- if (ret != 0) {
- qerror_report(QERR_SET_PASSWD_FAILED);
+ if (vs->auth == VNC_AUTH_NONE) {
+ vs->auth = VNC_AUTH_VNC;
}
- return ret;
+
+ return 0;
}
int vnc_display_pw_expire(DisplayState *ds, time_t expires)
{
VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display;
+ if (!vs) {
+ return -EINVAL;
+ }
+
vs->expires = expires;
return 0;
}
return vnc_socket_local_addr("%s:%s", vs->lsock);
}
-int vnc_display_open(DisplayState *ds, const char *display)
+void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
{
VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display;
const char *options;
#endif
int lock_key_sync = 1;
- if (!vnc_display)
- return -1;
+ if (!vnc_display) {
+ error_setg(errp, "VNC display not active");
+ return;
+ }
vnc_display_close(ds);
if (strcmp(display, "none") == 0)
- return 0;
+ return;
- if (!(vs->display = strdup(display)))
- return -1;
+ vs->display = g_strdup(display);
+ vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
options = display;
while ((options = strchr(options, ','))) {
options++;
if (strncmp(options, "password", 8) == 0) {
+ if (fips_get_state()) {
+ error_setg(errp,
+ "VNC password auth disabled due to FIPS mode, "
+ "consider using the VeNCrypt or SASL authentication "
+ "methods as an alternative");
+ goto fail;
+ }
password = 1; /* Require password auth */
} else if (strncmp(options, "reverse", 7) == 0) {
reverse = 1;
- } else if (strncmp(options, "no-lock-key-sync", 9) == 0) {
+ } else if (strncmp(options, "no-lock-key-sync", 16) == 0) {
lock_key_sync = 0;
#ifdef CONFIG_VNC_SASL
} else if (strncmp(options, "sasl", 4) == 0) {
VNC_DEBUG("Trying certificate path '%s'\n", path);
if (vnc_tls_set_x509_creds_dir(vs, path) < 0) {
- fprintf(stderr, "Failed to find x509 certificates/keys in %s\n", path);
+ error_setg(errp, "Failed to find x509 certificates/keys in %s", path);
g_free(path);
- g_free(vs->display);
- vs->display = NULL;
- return -1;
+ goto fail;
}
g_free(path);
} else {
- fprintf(stderr, "No certificate path provided\n");
- g_free(vs->display);
- vs->display = NULL;
- return -1;
+ error_setg(errp, "No certificate path provided");
+ goto fail;
}
#endif
#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
vs->lossy = true;
} else if (strncmp(options, "non-adapative", 13) == 0) {
vs->non_adaptive = true;
+ } else if (strncmp(options, "share=", 6) == 0) {
+ if (strncmp(options+6, "ignore", 6) == 0) {
+ vs->share_policy = VNC_SHARE_POLICY_IGNORE;
+ } else if (strncmp(options+6, "allow-exclusive", 15) == 0) {
+ vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
+ } else if (strncmp(options+6, "force-shared", 12) == 0) {
+ vs->share_policy = VNC_SHARE_POLICY_FORCE_SHARED;
+ } else {
+ error_setg(errp, "unknown vnc share= option");
+ goto fail;
+ }
}
}
#ifdef CONFIG_VNC_SASL
if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) {
- fprintf(stderr, "Failed to initialize SASL auth %s",
- sasl_errstring(saslErr, NULL, NULL));
- g_free(vs->display);
- vs->display = NULL;
- return -1;
+ error_setg(errp, "Failed to initialize SASL auth: %s",
+ sasl_errstring(saslErr, NULL, NULL));
+ goto fail;
}
#endif
vs->lock_key_sync = lock_key_sync;
if (reverse) {
/* connect to viewer */
- if (strncmp(display, "unix:", 5) == 0)
- vs->lsock = unix_connect(display+5);
- else
- vs->lsock = inet_connect(display, SOCK_STREAM);
- if (-1 == vs->lsock) {
- g_free(vs->display);
- vs->display = NULL;
- return -1;
+ int csock;
+ vs->lsock = -1;
+ if (strncmp(display, "unix:", 5) == 0) {
+ csock = unix_connect(display+5, errp);
} else {
- int csock = vs->lsock;
- vs->lsock = -1;
- vnc_connect(vs, csock, 0);
+ csock = inet_connect(display, errp);
}
- return 0;
-
+ if (csock < 0) {
+ goto fail;
+ }
+ vnc_connect(vs, csock, 0);
} else {
/* listen for connects */
char *dpy;
dpy = g_malloc(256);
if (strncmp(display, "unix:", 5) == 0) {
pstrcpy(dpy, 256, "unix:");
- vs->lsock = unix_listen(display+5, dpy+5, 256-5);
+ vs->lsock = unix_listen(display+5, dpy+5, 256-5, errp);
} else {
- vs->lsock = inet_listen(display, dpy, 256, SOCK_STREAM, 5900);
+ vs->lsock = inet_listen(display, dpy, 256,
+ SOCK_STREAM, 5900, errp);
}
- if (-1 == vs->lsock) {
+ if (vs->lsock < 0) {
g_free(dpy);
- return -1;
- } else {
- g_free(vs->display);
- vs->display = dpy;
+ goto fail;
}
+ g_free(vs->display);
+ vs->display = dpy;
+ qemu_set_fd_handler2(vs->lsock, NULL, vnc_listen_read, NULL, vs);
}
- return qemu_set_fd_handler2(vs->lsock, NULL, vnc_listen_read, NULL, vs);
+ return;
+
+fail:
+ g_free(vs->display);
+ vs->display = NULL;
}
void vnc_display_add_client(DisplayState *ds, int csock, int skipauth)
{
VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display;
- return vnc_connect(vs, csock, skipauth);
+ vnc_connect(vs, csock, skipauth);
}