#include "vnc.h"
#include "vnc-jobs.h"
#include "trace.h"
+#include "hw/qdev-core.h"
#include "sysemu/sysemu.h"
#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
#include "qemu/option.h"
#include "qemu/sockets.h"
#include "qemu/timer.h"
-#include "qemu/acl.h"
+#include "authz/list.h"
#include "qemu/config-file.h"
-#include "qapi/qapi-events.h"
+#include "qapi/qapi-emit-events.h"
+#include "qapi/qapi-events-ui.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-ui.h"
#include "ui/input.h"
#include "crypto/hash.h"
#include "crypto/tlscredsanon.h"
#include "crypto/tlscredsx509.h"
+#include "crypto/random.h"
#include "qom/object_interfaces.h"
#include "qemu/cutils.h"
#include "io/dns-resolver.h"
QTAILQ_HEAD_INITIALIZER(vnc_displays);
static int vnc_cursor_define(VncState *vs);
-static void vnc_release_modifiers(VncState *vs);
static void vnc_update_throttle_offset(VncState *vs);
static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
}
QTAILQ_FOREACH(vs, &vd->clients, next) {
vnc_lock_output(vs);
+ if (vs->update == VNC_STATE_UPDATE_NONE &&
+ vs->job_update != VNC_STATE_UPDATE_NONE) {
+ /* job aborted before completion */
+ vs->update = vs->job_update;
+ vs->job_update = VNC_STATE_UPDATE_NONE;
+ }
vs->abort = false;
vnc_unlock_output(vs);
}
int bps;
switch (vs->as.fmt) {
default:
- case AUD_FMT_U8:
- case AUD_FMT_S8:
+ case AUDIO_FORMAT_U8:
+ case AUDIO_FORMAT_S8:
bps = 1;
break;
- case AUD_FMT_U16:
- case AUD_FMT_S16:
+ case AUDIO_FORMAT_U16:
+ case AUDIO_FORMAT_S16:
bps = 2;
break;
- case AUD_FMT_U32:
- case AUD_FMT_S32:
+ case AUDIO_FORMAT_U32:
+ case AUDIO_FORMAT_S32:
bps = 4;
break;
}
vnc_sasl_client_cleanup(vs);
#endif /* CONFIG_VNC_SASL */
audio_del(vs);
- vnc_release_modifiers(vs);
+ qkbd_state_lift_all_keys(vs->vd->kbd);
if (vs->mouse_mode_notifier.notify != NULL) {
qemu_remove_mouse_mode_change_notifier(&vs->mouse_mode_notifier);
qemu_input_event_sync();
}
-static void reset_keys(VncState *vs)
-{
- int i;
- for(i = 0; i < 256; i++) {
- if (vs->modifiers_state[i]) {
- qemu_input_event_send_key_number(vs->vd->dcl.con, i, false);
- qemu_input_event_send_key_delay(vs->vd->key_delay_ms);
- vs->modifiers_state[i] = 0;
- }
- }
-}
-
-static void press_key(VncState *vs, int keysym)
+static void press_key(VncState *vs, QKeyCode qcode)
{
- int keycode = keysym2scancode(vs->vd->kbd_layout, keysym,
- false, false, false) & SCANCODE_KEYMASK;
- qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, true);
- qemu_input_event_send_key_delay(vs->vd->key_delay_ms);
- qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, false);
- qemu_input_event_send_key_delay(vs->vd->key_delay_ms);
+ qkbd_state_key_event(vs->vd->kbd, qcode, true);
+ qkbd_state_key_event(vs->vd->kbd, qcode, false);
}
static void vnc_led_state_change(VncState *vs)
static void do_key_event(VncState *vs, int down, int keycode, int sym)
{
+ QKeyCode qcode = qemu_input_key_number_to_qcode(keycode);
+
/* QEMU console switch */
- switch(keycode) {
- case 0x2a: /* Left Shift */
- case 0x36: /* Right Shift */
- case 0x1d: /* Left CTRL */
- case 0x9d: /* Right CTRL */
- case 0x38: /* Left ALT */
- case 0xb8: /* Right ALT */
- if (down)
- vs->modifiers_state[keycode] = 1;
- else
- vs->modifiers_state[keycode] = 0;
- break;
- case 0x02 ... 0x0a: /* '1' to '9' keys */
- if (vs->vd->dcl.con == NULL &&
- down && vs->modifiers_state[0x1d] && vs->modifiers_state[0x38]) {
+ switch (qcode) {
+ case Q_KEY_CODE_1 ... Q_KEY_CODE_9: /* '1' to '9' keys */
+ if (vs->vd->dcl.con == NULL && down &&
+ qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_CTRL) &&
+ qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_ALT)) {
/* Reset the modifiers sent to the current console */
- reset_keys(vs);
- console_select(keycode - 0x02);
+ qkbd_state_lift_all_keys(vs->vd->kbd);
+ console_select(qcode - Q_KEY_CODE_1);
return;
}
- break;
- case 0x3a: /* CapsLock */
- case 0x45: /* NumLock */
- if (down)
- vs->modifiers_state[keycode] ^= 1;
+ default:
break;
}
toggles numlock away from the VNC window.
*/
if (keysym_is_numlock(vs->vd->kbd_layout, sym & 0xFFFF)) {
- if (!vs->modifiers_state[0x45]) {
+ if (!qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_NUMLOCK)) {
trace_vnc_key_sync_numlock(true);
- vs->modifiers_state[0x45] = 1;
- press_key(vs, 0xff7f);
+ press_key(vs, Q_KEY_CODE_NUM_LOCK);
}
} else {
- if (vs->modifiers_state[0x45]) {
+ if (qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_NUMLOCK)) {
trace_vnc_key_sync_numlock(false);
- vs->modifiers_state[0x45] = 0;
- press_key(vs, 0xff7f);
+ press_key(vs, Q_KEY_CODE_NUM_LOCK);
}
}
}
toggles capslock away from the VNC window.
*/
int uppercase = !!(sym >= 'A' && sym <= 'Z');
- int shift = !!(vs->modifiers_state[0x2a] | vs->modifiers_state[0x36]);
- int capslock = !!(vs->modifiers_state[0x3a]);
+ bool shift = qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_SHIFT);
+ bool capslock = qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_CAPSLOCK);
if (capslock) {
if (uppercase == shift) {
trace_vnc_key_sync_capslock(false);
- vs->modifiers_state[0x3a] = 0;
- press_key(vs, 0xffe5);
+ press_key(vs, Q_KEY_CODE_CAPS_LOCK);
}
} else {
if (uppercase != shift) {
trace_vnc_key_sync_capslock(true);
- vs->modifiers_state[0x3a] = 1;
- press_key(vs, 0xffe5);
+ press_key(vs, Q_KEY_CODE_CAPS_LOCK);
}
}
}
- if (qemu_console_is_graphic(NULL)) {
- qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, down);
- qemu_input_event_send_key_delay(vs->vd->key_delay_ms);
- } else {
- bool numlock = vs->modifiers_state[0x45];
- bool control = (vs->modifiers_state[0x1d] ||
- vs->modifiers_state[0x9d]);
+ qkbd_state_key_event(vs->vd->kbd, qcode, down);
+ if (!qemu_console_is_graphic(NULL)) {
+ bool numlock = qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_NUMLOCK);
+ bool control = qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_CTRL);
/* QEMU console emulation */
if (down) {
switch (keycode) {
}
}
-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 (!qemu_console_is_graphic(NULL)) {
- return;
- }
- for (i = 0; i < ARRAY_SIZE(keycodes); i++) {
- keycode = keycodes[i];
- if (!vs->modifiers_state[keycode]) {
- continue;
- }
- qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, false);
- qemu_input_event_send_key_delay(vs->vd->key_delay_ms);
- }
-}
-
static const char *code2name(int keycode)
{
return QKeyCode_str(qemu_input_key_number_to_qcode(keycode));
static void key_event(VncState *vs, int down, uint32_t sym)
{
- bool shift = vs->modifiers_state[0x2a] || vs->modifiers_state[0x36];
- bool altgr = vs->modifiers_state[0xb8];
- bool ctrl = vs->modifiers_state[0x1d] || vs->modifiers_state[0x9d];
int keycode;
int lsym = sym;
}
keycode = keysym2scancode(vs->vd->kbd_layout, lsym & 0xFFFF,
- shift, altgr, ctrl) & SCANCODE_KEYMASK;
+ vs->vd->kbd, down) & SCANCODE_KEYMASK;
trace_vnc_key_event_map(down, sym, keycode, code2name(keycode));
do_key_event(vs, down, keycode, sym);
}
if (len == 4)
return 10;
switch (read_u8(data, 4)) {
- case 0: vs->as.fmt = AUD_FMT_U8; break;
- case 1: vs->as.fmt = AUD_FMT_S8; break;
- case 2: vs->as.fmt = AUD_FMT_U16; break;
- case 3: vs->as.fmt = AUD_FMT_S16; break;
- case 4: vs->as.fmt = AUD_FMT_U32; break;
- case 5: vs->as.fmt = AUD_FMT_S32; break;
+ case 0: vs->as.fmt = AUDIO_FORMAT_U8; break;
+ case 1: vs->as.fmt = AUDIO_FORMAT_S8; break;
+ case 2: vs->as.fmt = AUDIO_FORMAT_U16; break;
+ case 3: vs->as.fmt = AUDIO_FORMAT_S16; break;
+ case 4: vs->as.fmt = AUDIO_FORMAT_U32; break;
+ case 5: vs->as.fmt = AUDIO_FORMAT_S32; break;
default:
VNC_DEBUG("Invalid audio format %d\n", read_u8(data, 4));
vnc_client_error(vs);
vnc_read_when(vs, protocol_client_init, 1);
}
-static void make_challenge(VncState *vs)
+static void authentication_failed(VncState *vs)
{
- int i;
-
- srand(time(NULL)+getpid()+getpid()*987654+rand());
-
- for (i = 0 ; i < sizeof(vs->challenge) ; i++)
- vs->challenge[i] = (int) (256.0*rand()/(RAND_MAX+1.0));
+ 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);
}
static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len)
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);
+ authentication_failed(vs);
qcrypto_cipher_free(cipher);
return 0;
}
void start_auth_vnc(VncState *vs)
{
- make_challenge(vs);
+ Error *err = NULL;
+
+ if (qcrypto_random_bytes(vs->challenge, sizeof(vs->challenge), &err)) {
+ trace_vnc_auth_fail(vs, vs->auth, "cannot get random bytes",
+ error_get_pretty(err));
+ error_free(err);
+ authentication_failed(vs);
+ return;
+ }
+
/* Send client a 'random' challenge */
vnc_write(vs, vs->challenge, sizeof(vs->challenge));
vnc_flush(vs);
* must pick the one we sent. Verify this */
if (data[0] != vs->auth) { /* Reject auth */
trace_vnc_auth_reject(vs, vs->auth, (int)data[0]);
- vnc_write_u32(vs, 1);
- if (vs->minor >= 8) {
- static const char err[] = "Authentication failed";
- vnc_write_u32(vs, sizeof(err));
- vnc_write(vs, err, sizeof(err));
- }
- vnc_client_error(vs);
+ authentication_failed(vs);
} else { /* Accept requested auth */
trace_vnc_auth_start(vs, vs->auth);
switch (vs->auth) {
default: /* Should not be possible, but just in case */
trace_vnc_auth_fail(vs, vs->auth, "Unhandled auth method", "");
- vnc_write_u8(vs, 1);
- if (vs->minor >= 8) {
- static const char err[] = "Authentication failed";
- vnc_write_u32(vs, sizeof(err));
- vnc_write(vs, err, sizeof(err));
- }
- vnc_client_error(vs);
+ authentication_failed(vs);
}
}
return 0;
vs->as.freq = 44100;
vs->as.nchannels = 2;
- vs->as.fmt = AUD_FMT_S16;
+ vs->as.fmt = AUDIO_FORMAT_S16;
vs->as.endianness = 0;
qemu_mutex_init(&vs->output_mutex);
vd->dcl.ops = &dcl_ops;
register_displaychangelistener(&vd->dcl);
+ vd->kbd = qkbd_state_init(vd->dcl.con);
}
object_unparent(OBJECT(vd->tlscreds));
vd->tlscreds = NULL;
}
- g_free(vd->tlsaclname);
- vd->tlsaclname = NULL;
+ if (vd->tlsauthz) {
+ object_unparent(OBJECT(vd->tlsauthz));
+ vd->tlsauthz = NULL;
+ }
+ g_free(vd->tlsauthzid);
+ vd->tlsauthzid = NULL;
if (vd->lock_key_sync) {
qemu_remove_led_event_handler(vd->led);
vd->led = NULL;
}
+#ifdef CONFIG_VNC_SASL
+ if (vd->sasl.authz) {
+ object_unparent(OBJECT(vd->sasl.authz));
+ vd->sasl.authz = NULL;
+ }
+ g_free(vd->sasl.authzid);
+ vd->sasl.authzid = NULL;
+#endif
}
int vnc_display_password(const char *id, const char *password)
},{
.name = "acl",
.type = QEMU_OPT_BOOL,
+ },{
+ .name = "tls-authz",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "sasl-authz",
+ .type = QEMU_OPT_STRING,
},{
.name = "lossy",
.type = QEMU_OPT_BOOL,
const char *credid;
bool sasl = false;
int acl = 0;
+ const char *tlsauthz;
+ const char *saslauthz;
int lock_key_sync = 1;
int key_delay_ms;
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;
+ }
share = qemu_opt_get(opts, "share");
if (share) {
vd->non_adaptive = true;
}
- if (acl) {
+ if (tlsauthz) {
+ vd->tlsauthzid = g_strdup(tlsauthz);
+ } else if (acl) {
if (strcmp(vd->id, "default") == 0) {
- vd->tlsaclname = g_strdup("vnc.x509dname");
+ vd->tlsauthzid = g_strdup("vnc.x509dname");
} else {
- vd->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vd->id);
+ vd->tlsauthzid = g_strdup_printf("vnc.%s.x509dname", vd->id);
}
- qemu_acl_init(vd->tlsaclname);
+ vd->tlsauthz = QAUTHZ(qauthz_list_new(vd->tlsauthzid,
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort));
}
#ifdef CONFIG_VNC_SASL
- if (acl && sasl) {
- char *aclname;
-
- if (strcmp(vd->id, "default") == 0) {
- aclname = g_strdup("vnc.username");
- } else {
- aclname = g_strdup_printf("vnc.%s.username", vd->id);
+ 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));
}
- vd->sasl.acl = qemu_acl_init(aclname);
- g_free(aclname);
}
#endif
vd->led = qemu_add_led_event_handler(kbd_leds, vd);
}
vd->ledstate = 0;
- vd->key_delay_ms = key_delay_ms;
device_id = qemu_opt_get(opts, "display");
if (device_id) {
}
if (con != vd->dcl.con) {
+ qkbd_state_free(vd->kbd);
unregister_displaychangelistener(&vd->dcl);
vd->dcl.con = con;
register_displaychangelistener(&vd->dcl);
+ vd->kbd = qkbd_state_init(vd->dcl.con);
}
+ qkbd_state_set_delay(vd->kbd, key_delay_ms);
if (saddr == NULL) {
goto cleanup;