X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/7084851534c834f00652f90a9da5e4032bd22130..42fb2807d9886e485652e7512398b9d5a83bd768:/vnc.c diff --git a/vnc.c b/vnc.c index 742ce9f24d..e1ff4edd75 100644 --- a/vnc.c +++ b/vnc.c @@ -1,9 +1,9 @@ /* * QEMU VNC display driver - * + * * Copyright (C) 2006 Anthony Liguori * Copyright (C) 2006 Fabrice Bellard - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -23,8 +23,12 @@ * THE SOFTWARE. */ -#include "vl.h" +#include "qemu-common.h" +#include "console.h" +#include "sysemu.h" #include "qemu_socket.h" +#include "qemu-timer.h" +#include "audio/audio.h" #define VNC_REFRESH_INTERVAL (1000 / 30) @@ -32,31 +36,44 @@ #include "keymaps.c" #include "d3des.h" -// #define _VNC_DEBUG +#ifdef CONFIG_VNC_TLS +#include +#include +#endif /* CONFIG_VNC_TLS */ + +// #define _VNC_DEBUG 1 #ifdef _VNC_DEBUG #define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) + +#if CONFIG_VNC_TLS && _VNC_DEBUG >= 2 +/* Very verbose, so only enabled for _VNC_DEBUG >= 2 */ +static void vnc_debug_gnutls_log(int level, const char* str) { + VNC_DEBUG("%d %s", level, str); +} +#endif /* CONFIG_VNC_TLS && _VNC_DEBUG */ #else #define VNC_DEBUG(fmt, ...) do { } while (0) #endif + typedef struct Buffer { size_t capacity; size_t offset; - char *buffer; + uint8_t *buffer; } Buffer; typedef struct VncState VncState; -typedef int VncReadEvent(VncState *vs, char *data, size_t len); +typedef int VncReadEvent(VncState *vs, uint8_t *data, size_t len); typedef void VncWritePixels(VncState *vs, void *data, int size); typedef void VncSendHextileTile(VncState *vs, int x, int y, int w, int h, - uint32_t *last_bg, - uint32_t *last_fg, + void *last_bg, + void *last_fg, int *has_bg, int *has_fg); #define VNC_MAX_WIDTH 2048 @@ -77,6 +94,29 @@ enum { VNC_AUTH_VENCRYPT = 19 }; +#ifdef CONFIG_VNC_TLS +enum { + VNC_WIREMODE_CLEAR, + VNC_WIREMODE_TLS, +}; + +enum { + VNC_AUTH_VENCRYPT_PLAIN = 256, + VNC_AUTH_VENCRYPT_TLSNONE = 257, + VNC_AUTH_VENCRYPT_TLSVNC = 258, + VNC_AUTH_VENCRYPT_TLSPLAIN = 259, + VNC_AUTH_VENCRYPT_X509NONE = 260, + VNC_AUTH_VENCRYPT_X509VNC = 261, + VNC_AUTH_VENCRYPT_X509PLAIN = 262, +}; + +#define X509_CA_CERT_FILE "ca-cert.pem" +#define X509_CA_CRL_FILE "ca-crl.pem" +#define X509_SERVER_KEY_FILE "server-key.pem" +#define X509_SERVER_CERT_FILE "server-cert.pem" + +#endif /* CONFIG_VNC_TLS */ + struct VncState { QEMUTimer *timer; @@ -92,6 +132,7 @@ struct VncState int has_resize; int has_hextile; int has_pointer_type_change; + int has_WMVi; int absolute; int last_x; int last_y; @@ -102,8 +143,22 @@ struct VncState char *display; char *password; int auth; +#ifdef CONFIG_VNC_TLS + int subauth; + int x509verify; + + char *x509cacert; + char *x509cacrl; + char *x509cert; + char *x509key; +#endif char challenge[VNC_AUTH_CHALLENGE_SIZE]; +#ifdef CONFIG_VNC_TLS + int wiremode; + gnutls_session_t tls_session; +#endif + Buffer output; Buffer input; kbd_layout_t *kbd_layout; @@ -111,9 +166,12 @@ struct VncState VncWritePixels *write_pixels; VncSendHextileTile *send_hextile_tile; int pix_bpp, pix_big_endian; - int red_shift, red_max, red_shift1; - int green_shift, green_max, green_shift1; - int blue_shift, blue_max, blue_shift1; + int client_red_shift, client_red_max, server_red_shift, server_red_max; + int client_green_shift, client_green_max, server_green_shift, server_green_max; + int client_blue_shift, client_blue_max, server_blue_shift, server_blue_max; + + CaptureVoiceOut *audio_cap; + struct audsettings as; VncReadEvent *read_handler; size_t read_handler_expect; @@ -125,7 +183,7 @@ static VncState *vnc_state; /* needed for info vnc */ void do_info_vnc(void) { - if (vnc_state == NULL) + if (vnc_state == NULL || vnc_state->display == NULL) term_printf("VNC server disabled\n"); else { term_printf("VNC server active on: "); @@ -155,6 +213,8 @@ static void vnc_flush(VncState *vs); static void vnc_update_client(void *opaque); static void vnc_client_read(void *opaque); +static void vnc_colordepth(DisplayState *ds, int depth); + static inline void vnc_set_bit(uint32_t *d, int k) { d[k >> 5] |= 1 << (k & 0x1f); @@ -174,7 +234,7 @@ static inline void vnc_set_bits(uint32_t *d, int n, int nb_words) d[j++] = -1; n -= 32; } - if (n > 0) + if (n > 0) d[j++] = (1 << n) - 1; while (j < nb_words) d[j++] = 0; @@ -185,7 +245,7 @@ static inline int vnc_get_bit(const uint32_t *d, int k) return (d[k >> 5] >> (k & 0x1f)) & 1; } -static inline int vnc_and_bits(const uint32_t *d1, const uint32_t *d2, +static inline int vnc_and_bits(const uint32_t *d1, const uint32_t *d2, int nb_words) { int i; @@ -203,6 +263,18 @@ static void vnc_dpy_update(DisplayState *ds, int x, int y, int w, int h) h += y; + /* round x down to ensure the loop only spans one 16-pixel block per, + iteration. otherwise, if (x % 16) != 0, the last iteration may span + two 16-pixel blocks but we only mark the first as dirty + */ + w += (x % 16); + x -= (x % 16); + + x = MIN(x, vs->width); + y = MIN(y, vs->height); + w = MIN(x + w, vs->width) - x; + h = MIN(h, vs->height); + for (; y < h; y++) for (i = 0; i < w; i += 16) vnc_set_bit(vs->dirty_row[y], (x + i) / 16); @@ -224,28 +296,36 @@ static void vnc_dpy_resize(DisplayState *ds, int w, int h) int size_changed; VncState *vs = ds->opaque; - ds->data = realloc(ds->data, w * h * vs->depth); - vs->old_data = realloc(vs->old_data, w * h * vs->depth); + ds->data = qemu_realloc(ds->data, w * h * vs->depth); + vs->old_data = qemu_realloc(vs->old_data, w * h * vs->depth); if (ds->data == NULL || vs->old_data == NULL) { fprintf(stderr, "vnc: memory allocation failed\n"); exit(1); } - ds->depth = vs->depth * 8; + if (ds->depth != vs->depth * 8) { + ds->depth = vs->depth * 8; + console_color_init(ds); + } size_changed = ds->width != w || ds->height != h; ds->width = w; ds->height = h; ds->linesize = w * vs->depth; - if (vs->csock != -1 && vs->has_resize && size_changed) { - vnc_write_u8(vs, 0); /* msg id */ - vnc_write_u8(vs, 0); - vnc_write_u16(vs, 1); /* number of rects */ - vnc_framebuffer_update(vs, 0, 0, ds->width, ds->height, -223); - vnc_flush(vs); - vs->width = ds->width; - vs->height = ds->height; + if (size_changed) { + vs->width = ds->width; + vs->height = ds->height; + if (vs->csock != -1 && vs->has_resize) { + vnc_write_u8(vs, 0); /* msg id */ + vnc_write_u8(vs, 0); + vnc_write_u16(vs, 1); /* number of rects */ + vnc_framebuffer_update(vs, 0, 0, ds->width, ds->height, -223); + vnc_flush(vs); + } } + + memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); + memset(vs->old_data, 42, ds_get_linesize(vs->ds) * ds_get_height(vs->ds)); } /* fastest code */ @@ -257,14 +337,17 @@ static void vnc_write_pixels_copy(VncState *vs, void *pixels, int size) /* slowest but generic code. */ static void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v) { - unsigned int r, g, b; - - r = (v >> vs->red_shift1) & vs->red_max; - g = (v >> vs->green_shift1) & vs->green_max; - b = (v >> vs->blue_shift1) & vs->blue_max; - v = (r << vs->red_shift) | - (g << vs->green_shift) | - (b << vs->blue_shift); + uint8_t r, g, b; + + r = ((v >> vs->server_red_shift) & vs->server_red_max) * (vs->client_red_max + 1) / + (vs->server_red_max + 1); + g = ((v >> vs->server_green_shift) & vs->server_green_max) * (vs->client_green_max + 1) / + (vs->server_green_max + 1); + b = ((v >> vs->server_blue_shift) & vs->server_blue_max) * (vs->client_blue_max + 1) / + (vs->server_blue_max + 1); + v = (r << vs->client_red_shift) | + (g << vs->client_green_shift) | + (b << vs->client_blue_shift); switch(vs->pix_bpp) { case 1: buf[0] = v; @@ -297,28 +380,48 @@ static void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v) static void vnc_write_pixels_generic(VncState *vs, void *pixels1, int size) { - uint32_t *pixels = pixels1; uint8_t buf[4]; - int n, i; - n = size >> 2; - for(i = 0; i < n; i++) { - vnc_convert_pixel(vs, buf, pixels[i]); - vnc_write(vs, buf, vs->pix_bpp); + if (vs->depth == 4) { + uint32_t *pixels = pixels1; + int n, i; + n = size >> 2; + for(i = 0; i < n; i++) { + vnc_convert_pixel(vs, buf, pixels[i]); + vnc_write(vs, buf, vs->pix_bpp); + } + } else if (vs->depth == 2) { + uint16_t *pixels = pixels1; + int n, i; + n = size >> 1; + for(i = 0; i < n; i++) { + vnc_convert_pixel(vs, buf, pixels[i]); + vnc_write(vs, buf, vs->pix_bpp); + } + } else if (vs->depth == 1) { + uint8_t *pixels = pixels1; + int n, i; + n = size; + for(i = 0; i < n; i++) { + vnc_convert_pixel(vs, buf, pixels[i]); + vnc_write(vs, buf, vs->pix_bpp); + } + } else { + fprintf(stderr, "vnc_write_pixels_generic: VncState color depth not supported\n"); } } static void send_framebuffer_update_raw(VncState *vs, int x, int y, int w, int h) { int i; - char *row; + uint8_t *row; vnc_framebuffer_update(vs, x, y, w, h, 0); - row = vs->ds->data + y * vs->ds->linesize + x * vs->depth; + row = ds_get_data(vs->ds) + y * ds_get_linesize(vs->ds) + x * vs->depth; for (i = 0; i < h; i++) { vs->write_pixels(vs, row, w * vs->depth); - row += vs->ds->linesize; + row += ds_get_linesize(vs->ds); } } @@ -340,6 +443,18 @@ static void hextile_enc_cord(uint8_t *ptr, int x, int y, int w, int h) #include "vnchextile.h" #undef BPP +#define GENERIC +#define BPP 8 +#include "vnchextile.h" +#undef BPP +#undef GENERIC + +#define GENERIC +#define BPP 16 +#include "vnchextile.h" +#undef BPP +#undef GENERIC + #define GENERIC #define BPP 32 #include "vnchextile.h" @@ -350,18 +465,23 @@ static void send_framebuffer_update_hextile(VncState *vs, int x, int y, int w, i { int i, j; int has_fg, has_bg; - uint32_t last_fg32, last_bg32; + uint8_t *last_fg, *last_bg; vnc_framebuffer_update(vs, x, y, w, h, 5); + last_fg = (uint8_t *) malloc(vs->depth); + last_bg = (uint8_t *) malloc(vs->depth); has_fg = has_bg = 0; for (j = y; j < (y + h); j += 16) { for (i = x; i < (x + w); i += 16) { - vs->send_hextile_tile(vs, i, j, + vs->send_hextile_tile(vs, i, j, MIN(16, x + w - i), MIN(16, y + h - j), - &last_bg32, &last_fg32, &has_bg, &has_fg); + last_bg, last_fg, &has_bg, &has_fg); } } + free(last_fg); + free(last_bg); + } static void send_framebuffer_update(VncState *vs, int x, int y, int w, int h) @@ -375,11 +495,11 @@ static void send_framebuffer_update(VncState *vs, int x, int y, int w, int h) static void vnc_copy(DisplayState *ds, int src_x, int src_y, int dst_x, int dst_y, int w, int h) { int src, dst; - char *src_row; - char *dst_row; + uint8_t *src_row; + uint8_t *dst_row; char *old_row; int y = 0; - int pitch = ds->linesize; + int pitch = ds_get_linesize(ds); VncState *vs = ds->opaque; vnc_update_client(vs); @@ -389,11 +509,11 @@ static void vnc_copy(DisplayState *ds, int src_x, int src_y, int dst_x, int dst_ pitch = -pitch; } - src = (ds->linesize * (src_y + y) + vs->depth * src_x); - dst = (ds->linesize * (dst_y + y) + vs->depth * dst_x); + src = (ds_get_linesize(ds) * (src_y + y) + vs->depth * src_x); + dst = (ds_get_linesize(ds) * (dst_y + y) + vs->depth * dst_x); - src_row = ds->data + src; - dst_row = ds->data + dst; + src_row = ds_get_data(ds) + src; + dst_row = ds_get_data(ds) + dst; old_row = vs->old_data + dst; for (y = 0; y < h; y++) { @@ -434,29 +554,32 @@ static void vnc_update_client(void *opaque) if (vs->need_update && vs->csock != -1) { int y; - char *row; + uint8_t *row; char *old_row; uint32_t width_mask[VNC_DIRTY_WORDS]; int n_rectangles; int saved_offset; int has_dirty = 0; + vga_hw_update(); + vnc_set_bits(width_mask, (vs->width / 16), VNC_DIRTY_WORDS); /* Walk through the dirty map and eliminate tiles that really aren't dirty */ - row = vs->ds->data; + row = ds_get_data(vs->ds); old_row = vs->old_data; for (y = 0; y < vs->height; y++) { if (vnc_and_bits(vs->dirty_row[y], width_mask, VNC_DIRTY_WORDS)) { int x; - char *ptr, *old_ptr; + uint8_t *ptr; + char *old_ptr; ptr = row; - old_ptr = old_row; + old_ptr = (char*)old_row; - for (x = 0; x < vs->ds->width; x += 16) { + for (x = 0; x < ds_get_width(vs->ds); x += 16) { if (memcmp(old_ptr, ptr, 16 * vs->depth) == 0) { vnc_clear_bit(vs->dirty_row[y], (x / 16)); } else { @@ -469,11 +592,11 @@ static void vnc_update_client(void *opaque) } } - row += vs->ds->linesize; - old_row += vs->ds->linesize; + row += ds_get_linesize(vs->ds); + old_row += ds_get_linesize(vs->ds); } - if (!has_dirty) { + if (!has_dirty && !vs->audio_cap) { qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock) + VNC_REFRESH_INTERVAL); return; } @@ -514,22 +637,11 @@ static void vnc_update_client(void *opaque) vnc_flush(vs); } - qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock) + VNC_REFRESH_INTERVAL); -} -static void vnc_timer_init(VncState *vs) -{ - if (vs->timer == NULL) { - vs->timer = qemu_new_timer(rt_clock, vnc_update_client, vs); - qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock)); + if (vs->csock != -1) { + qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock) + VNC_REFRESH_INTERVAL); } -} -static void vnc_dpy_refresh(DisplayState *ds) -{ - VncState *vs = ds->opaque; - vnc_timer_init(vs); - vga_hw_update(); } static int vnc_listen_poll(void *opaque) @@ -544,7 +656,7 @@ static void buffer_reserve(Buffer *buffer, size_t len) { if ((buffer->capacity - buffer->offset) < len) { buffer->capacity += (len + 1024); - buffer->buffer = realloc(buffer->buffer, buffer->capacity); + buffer->buffer = qemu_realloc(buffer->buffer, buffer->capacity); if (buffer->buffer == NULL) { fprintf(stderr, "vnc: out of memory\n"); exit(1); @@ -557,7 +669,7 @@ static int buffer_empty(Buffer *buffer) return buffer->offset == 0; } -static char *buffer_end(Buffer *buffer) +static uint8_t *buffer_end(Buffer *buffer) { return buffer->buffer + buffer->offset; } @@ -573,18 +685,103 @@ static void buffer_append(Buffer *buffer, const void *data, size_t len) buffer->offset += len; } +/* audio */ +static void audio_capture_notify(void *opaque, audcnotification_e cmd) +{ + VncState *vs = opaque; + + switch (cmd) { + case AUD_CNOTIFY_DISABLE: + vnc_write_u8(vs, 255); + vnc_write_u8(vs, 1); + vnc_write_u16(vs, 0); + vnc_flush(vs); + break; + + case AUD_CNOTIFY_ENABLE: + vnc_write_u8(vs, 255); + vnc_write_u8(vs, 1); + vnc_write_u16(vs, 1); + vnc_flush(vs); + break; + } +} + +static void audio_capture_destroy(void *opaque) +{ +} + +static void audio_capture(void *opaque, void *buf, int size) +{ + VncState *vs = opaque; + + vnc_write_u8(vs, 255); + vnc_write_u8(vs, 1); + vnc_write_u16(vs, 2); + vnc_write_u32(vs, size); + vnc_write(vs, buf, size); + vnc_flush(vs); +} + +static void audio_add(VncState *vs) +{ + struct audio_capture_ops ops; + + if (vs->audio_cap) { + term_printf ("audio already running\n"); + return; + } + + ops.notify = audio_capture_notify; + ops.destroy = audio_capture_destroy; + ops.capture = audio_capture; + + vs->audio_cap = AUD_add_capture(NULL, &vs->as, &ops, vs); + if (!vs->audio_cap) { + term_printf ("Failed to add audio capture\n"); + } +} + +static void audio_del(VncState *vs) +{ + if (vs->audio_cap) { + AUD_del_capture(vs->audio_cap, vs); + vs->audio_cap = NULL; + } +} + static int vnc_client_io_error(VncState *vs, int ret, int last_errno) { if (ret == 0 || ret == -1) { - if (ret == -1 && (last_errno == EINTR || last_errno == EAGAIN)) - return 0; + if (ret == -1) { + switch (last_errno) { + case EINTR: + case EAGAIN: +#ifdef _WIN32 + case WSAEWOULDBLOCK: +#endif + return 0; + default: + break; + } + } + VNC_DEBUG("Closing down client sock %d %d\n", ret, ret < 0 ? last_errno : 0); qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL); closesocket(vs->csock); vs->csock = -1; + vs->ds->idle = 1; buffer_reset(&vs->input); buffer_reset(&vs->output); vs->need_update = 0; +#ifdef CONFIG_VNC_TLS + if (vs->tls_session) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + } + vs->wiremode = VNC_WIREMODE_CLEAR; +#endif /* CONFIG_VNC_TLS */ + audio_del(vs); return 0; } return ret; @@ -600,7 +797,19 @@ static void vnc_client_write(void *opaque) long ret; VncState *vs = opaque; - ret = send(vs->csock, vs->output.buffer, vs->output.offset, 0); +#ifdef CONFIG_VNC_TLS + if (vs->tls_session) { + ret = gnutls_write(vs->tls_session, vs->output.buffer, vs->output.offset); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EIO; + ret = -1; + } + } else +#endif /* CONFIG_VNC_TLS */ + ret = send(vs->csock, vs->output.buffer, vs->output.offset, 0); ret = vnc_client_io_error(vs, ret, socket_error()); if (!ret) return; @@ -626,7 +835,19 @@ static void vnc_client_read(void *opaque) buffer_reserve(&vs->input, 4096); - ret = recv(vs->csock, buffer_end(&vs->input), 4096, 0); +#ifdef CONFIG_VNC_TLS + if (vs->tls_session) { + ret = gnutls_read(vs->tls_session, buffer_end(&vs->input), 4096); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EIO; + ret = -1; + } + } else +#endif /* CONFIG_VNC_TLS */ + ret = recv(vs->csock, buffer_end(&vs->input), 4096, 0); ret = vnc_client_io_error(vs, ret, socket_error()); if (!ret) return; @@ -721,7 +942,42 @@ static uint32_t read_u32(uint8_t *data, size_t offset) (data[offset + 2] << 8) | data[offset + 3]); } -static void client_cut_text(VncState *vs, size_t len, char *text) +#ifdef CONFIG_VNC_TLS +static ssize_t vnc_tls_push(gnutls_transport_ptr_t transport, + const void *data, + size_t len) { + struct VncState *vs = (struct VncState *)transport; + int ret; + + retry: + ret = send(vs->csock, data, len, 0); + if (ret < 0) { + if (errno == EINTR) + goto retry; + return -1; + } + return ret; +} + + +static ssize_t vnc_tls_pull(gnutls_transport_ptr_t transport, + void *data, + size_t len) { + struct VncState *vs = (struct VncState *)transport; + int ret; + + retry: + ret = recv(vs->csock, data, len, 0); + if (ret < 0) { + if (errno == EINTR) + goto retry; + return -1; + } + return ret; +} +#endif /* CONFIG_VNC_TLS */ + +static void client_cut_text(VncState *vs, size_t len, uint8_t *text) { } @@ -732,7 +988,7 @@ static void check_pointer_type_change(VncState *vs, int absolute) vnc_write_u8(vs, 0); vnc_write_u16(vs, 1); vnc_framebuffer_update(vs, absolute, 0, - vs->ds->width, vs->ds->height, -257); + ds_get_width(vs->ds), ds_get_height(vs->ds), -257); vnc_flush(vs); } vs->absolute = absolute; @@ -755,8 +1011,8 @@ static void pointer_event(VncState *vs, int button_mask, int x, int y) dz = 1; if (vs->absolute) { - kbd_mouse_event(x * 0x7FFF / vs->ds->width, - y * 0x7FFF / vs->ds->height, + kbd_mouse_event(x * 0x7FFF / (ds_get_width(vs->ds) - 1), + y * 0x7FFF / (ds_get_height(vs->ds) - 1), dz, buttons); } else if (vs->has_pointer_type_change) { x -= 0x7FFF; @@ -788,12 +1044,14 @@ static void reset_keys(VncState *vs) } } -static void do_key_event(VncState *vs, int down, uint32_t sym) +static void press_key(VncState *vs, int keysym) { - int keycode; + kbd_put_keycode(keysym2scancode(vs->kbd_layout, keysym) & 0x7f); + kbd_put_keycode(keysym2scancode(vs->kbd_layout, keysym) | 0x80); +} - keycode = keysym2scancode(vs->kbd_layout, sym & 0xFFFF); - +static void do_key_event(VncState *vs, int down, int keycode, int sym) +{ /* QEMU console switch */ switch(keycode) { case 0x2a: /* Left Shift */ @@ -807,7 +1065,7 @@ static void do_key_event(VncState *vs, int down, uint32_t sym) else vs->modifiers_state[keycode] = 0; break; - case 0x02 ... 0x0a: /* '1' to '9' keys */ + case 0x02 ... 0x0a: /* '1' to '9' keys */ if (down && vs->modifiers_state[0x1d] && vs->modifiers_state[0x38]) { /* Reset the modifiers sent to the current console */ reset_keys(vs); @@ -815,6 +1073,29 @@ static void do_key_event(VncState *vs, int down, uint32_t sym) return; } break; + case 0x3a: /* CapsLock */ + case 0x45: /* NumLock */ + if (!down) + vs->modifiers_state[keycode] ^= 1; + break; + } + + if (keycode_is_keypad(vs->kbd_layout, keycode)) { + /* If the numlock state needs to change then simulate an additional + keypress before sending this one. This will happen if the user + toggles numlock away from the VNC window. + */ + if (keysym_is_numlock(vs->kbd_layout, sym & 0xFFFF)) { + if (!vs->modifiers_state[0x45]) { + vs->modifiers_state[0x45] = 1; + press_key(vs, 0xff7f); + } + } else { + if (vs->modifiers_state[0x45]) { + vs->modifiers_state[0x45] = 0; + press_key(vs, 0xff7f); + } + } } if (is_graphic_console()) { @@ -872,38 +1153,70 @@ static void do_key_event(VncState *vs, int down, uint32_t sym) static void key_event(VncState *vs, int down, uint32_t sym) { - if (sym >= 'A' && sym <= 'Z') + int keycode; + + if (sym >= 'A' && sym <= 'Z' && is_graphic_console()) sym = sym - 'A' + 'a'; - do_key_event(vs, down, sym); + + keycode = keysym2scancode(vs->kbd_layout, sym & 0xFFFF); + do_key_event(vs, down, keycode, sym); +} + +static void ext_key_event(VncState *vs, int down, + uint32_t sym, uint16_t keycode) +{ + /* if the user specifies a keyboard layout, always use it */ + if (keyboard_layout) + key_event(vs, down, sym); + else + do_key_event(vs, down, keycode, sym); } static void framebuffer_update_request(VncState *vs, int incremental, int x_position, int y_position, int w, int h) { - if (x_position > vs->ds->width) - x_position = vs->ds->width; - if (y_position > vs->ds->height) - y_position = vs->ds->height; - if (x_position + w >= vs->ds->width) - w = vs->ds->width - x_position; - if (y_position + h >= vs->ds->height) - h = vs->ds->height - y_position; + if (x_position > ds_get_width(vs->ds)) + x_position = ds_get_width(vs->ds); + if (y_position > ds_get_height(vs->ds)) + y_position = ds_get_height(vs->ds); + if (x_position + w >= ds_get_width(vs->ds)) + w = ds_get_width(vs->ds) - x_position; + if (y_position + h >= ds_get_height(vs->ds)) + h = ds_get_height(vs->ds) - y_position; int i; vs->need_update = 1; if (!incremental) { - char *old_row = vs->old_data + y_position * vs->ds->linesize; + char *old_row = vs->old_data + y_position * ds_get_linesize(vs->ds); for (i = 0; i < h; i++) { - vnc_set_bits(vs->dirty_row[y_position + i], - (vs->ds->width / 16), VNC_DIRTY_WORDS); - memset(old_row, 42, vs->ds->width * vs->depth); - old_row += vs->ds->linesize; + vnc_set_bits(vs->dirty_row[y_position + i], + (ds_get_width(vs->ds) / 16), VNC_DIRTY_WORDS); + memset(old_row, 42, ds_get_width(vs->ds) * vs->depth); + old_row += ds_get_linesize(vs->ds); } } } +static void send_ext_key_event_ack(VncState *vs) +{ + vnc_write_u8(vs, 0); + vnc_write_u8(vs, 0); + vnc_write_u16(vs, 1); + vnc_framebuffer_update(vs, 0, 0, ds_get_width(vs->ds), ds_get_height(vs->ds), -258); + vnc_flush(vs); +} + +static void send_ext_audio_ack(VncState *vs) +{ + vnc_write_u8(vs, 0); + vnc_write_u8(vs, 0); + vnc_write_u16(vs, 1); + vnc_framebuffer_update(vs, 0, 0, ds_get_width(vs->ds), ds_get_height(vs->ds), -259); + vnc_flush(vs); +} + static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) { int i; @@ -911,6 +1224,7 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) vs->has_hextile = 0; vs->has_resize = 0; vs->has_pointer_type_change = 0; + vs->has_WMVi = 0; vs->absolute = -1; vs->ds->dpy_copy = NULL; @@ -931,6 +1245,15 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) case -257: vs->has_pointer_type_change = 1; break; + case -258: + send_ext_key_event_ack(vs); + break; + case -259: + send_ext_audio_ack(vs); + break; + case 0x574D5669: + vs->has_WMVi = 1; + break; default: break; } @@ -939,17 +1262,6 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) check_pointer_type_change(vs, kbd_mouse_is_absolute()); } -static int compute_nbits(unsigned int val) -{ - int n; - n = 0; - while (val != 0) { - n++; - val >>= 1; - } - return n; -} - static void set_pixel_format(VncState *vs, int bits_per_pixel, int depth, int big_endian_flag, int true_color_flag, @@ -968,60 +1280,210 @@ static void set_pixel_format(VncState *vs, vnc_client_error(vs); return; } - if (bits_per_pixel == 32 && + if (bits_per_pixel == 32 && + bits_per_pixel == vs->depth * 8 && host_big_endian_flag == big_endian_flag && red_max == 0xff && green_max == 0xff && blue_max == 0xff && red_shift == 16 && green_shift == 8 && blue_shift == 0) { vs->depth = 4; vs->write_pixels = vnc_write_pixels_copy; vs->send_hextile_tile = send_hextile_tile_32; - } else - if (bits_per_pixel == 16 && + } else + if (bits_per_pixel == 16 && + bits_per_pixel == vs->depth * 8 && host_big_endian_flag == big_endian_flag && red_max == 31 && green_max == 63 && blue_max == 31 && red_shift == 11 && green_shift == 5 && blue_shift == 0) { vs->depth = 2; vs->write_pixels = vnc_write_pixels_copy; vs->send_hextile_tile = send_hextile_tile_16; - } else - if (bits_per_pixel == 8 && + } else + if (bits_per_pixel == 8 && + bits_per_pixel == vs->depth * 8 && red_max == 7 && green_max == 7 && blue_max == 3 && red_shift == 5 && green_shift == 2 && blue_shift == 0) { vs->depth = 1; vs->write_pixels = vnc_write_pixels_copy; vs->send_hextile_tile = send_hextile_tile_8; - } else + } else { /* generic and slower case */ if (bits_per_pixel != 8 && bits_per_pixel != 16 && bits_per_pixel != 32) goto fail; - vs->depth = 4; - vs->red_shift = red_shift; - vs->red_max = red_max; - vs->red_shift1 = 24 - compute_nbits(red_max); - vs->green_shift = green_shift; - vs->green_max = green_max; - vs->green_shift1 = 16 - compute_nbits(green_max); - vs->blue_shift = blue_shift; - vs->blue_max = blue_max; - vs->blue_shift1 = 8 - compute_nbits(blue_max); - vs->pix_bpp = bits_per_pixel / 8; + if (vs->depth == 4) { + vs->send_hextile_tile = send_hextile_tile_generic_32; + } else if (vs->depth == 2) { + vs->send_hextile_tile = send_hextile_tile_generic_16; + } else { + vs->send_hextile_tile = send_hextile_tile_generic_8; + } + vs->pix_big_endian = big_endian_flag; vs->write_pixels = vnc_write_pixels_generic; - vs->send_hextile_tile = send_hextile_tile_generic; } - vnc_dpy_resize(vs->ds, vs->ds->width, vs->ds->height); - memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); - memset(vs->old_data, 42, vs->ds->linesize * vs->ds->height); + vs->client_red_shift = red_shift; + vs->client_red_max = red_max; + vs->client_green_shift = green_shift; + vs->client_green_max = green_max; + vs->client_blue_shift = blue_shift; + vs->client_blue_max = blue_max; + vs->pix_bpp = bits_per_pixel / 8; vga_hw_invalidate(); vga_hw_update(); } -static int protocol_client_msg(VncState *vs, char *data, size_t len) +static void pixel_format_message (VncState *vs) { + char pad[3] = { 0, 0, 0 }; + + vnc_write_u8(vs, vs->depth * 8); /* bits-per-pixel */ + if (vs->depth == 4) vnc_write_u8(vs, 24); /* depth */ + else vnc_write_u8(vs, vs->depth * 8); /* depth */ + +#ifdef WORDS_BIGENDIAN + vnc_write_u8(vs, 1); /* big-endian-flag */ +#else + vnc_write_u8(vs, 0); /* big-endian-flag */ +#endif + vnc_write_u8(vs, 1); /* true-color-flag */ + if (vs->depth == 4) { + vnc_write_u16(vs, 0xFF); /* red-max */ + vnc_write_u16(vs, 0xFF); /* green-max */ + vnc_write_u16(vs, 0xFF); /* blue-max */ + vnc_write_u8(vs, 16); /* red-shift */ + vnc_write_u8(vs, 8); /* green-shift */ + vnc_write_u8(vs, 0); /* blue-shift */ + vs->send_hextile_tile = send_hextile_tile_32; + } else if (vs->depth == 2) { + vnc_write_u16(vs, 31); /* red-max */ + vnc_write_u16(vs, 63); /* green-max */ + vnc_write_u16(vs, 31); /* blue-max */ + vnc_write_u8(vs, 11); /* red-shift */ + vnc_write_u8(vs, 5); /* green-shift */ + vnc_write_u8(vs, 0); /* blue-shift */ + vs->send_hextile_tile = send_hextile_tile_16; + } else if (vs->depth == 1) { + /* XXX: change QEMU pixel 8 bit pixel format to match the VNC one ? */ + vnc_write_u16(vs, 7); /* red-max */ + vnc_write_u16(vs, 7); /* green-max */ + vnc_write_u16(vs, 3); /* blue-max */ + vnc_write_u8(vs, 5); /* red-shift */ + vnc_write_u8(vs, 2); /* green-shift */ + vnc_write_u8(vs, 0); /* blue-shift */ + vs->send_hextile_tile = send_hextile_tile_8; + } + vs->client_red_max = vs->server_red_max; + vs->client_green_max = vs->server_green_max; + vs->client_blue_max = vs->server_blue_max; + vs->client_red_shift = vs->server_red_shift; + vs->client_green_shift = vs->server_green_shift; + vs->client_blue_shift = vs->server_blue_shift; + vs->pix_bpp = vs->depth * 8; + vs->write_pixels = vnc_write_pixels_copy; + + vnc_write(vs, pad, 3); /* padding */ +} + +static void vnc_colordepth(DisplayState *ds, int depth) +{ + int host_big_endian_flag; + struct VncState *vs = ds->opaque; + + switch (depth) { + case 24: + if (ds->depth == 32) return; + depth = 32; + break; + case 15: + case 8: + case 0: + return; + default: + break; + } + +#ifdef WORDS_BIGENDIAN + host_big_endian_flag = 1; +#else + host_big_endian_flag = 0; +#endif + + switch (depth) { + case 8: + vs->depth = depth / 8; + vs->server_red_max = 7; + vs->server_green_max = 7; + vs->server_blue_max = 3; + vs->server_red_shift = 5; + vs->server_green_shift = 2; + vs->server_blue_shift = 0; + break; + case 16: + vs->depth = depth / 8; + vs->server_red_max = 31; + vs->server_green_max = 63; + vs->server_blue_max = 31; + vs->server_red_shift = 11; + vs->server_green_shift = 5; + vs->server_blue_shift = 0; + break; + case 32: + vs->depth = 4; + vs->server_red_max = 255; + vs->server_green_max = 255; + vs->server_blue_max = 255; + vs->server_red_shift = 16; + vs->server_green_shift = 8; + vs->server_blue_shift = 0; + break; + default: + return; + } + + if (vs->csock != -1 && vs->has_WMVi) { + /* Sending a WMVi message to notify the client*/ + vnc_write_u8(vs, 0); /* msg id */ + vnc_write_u8(vs, 0); + vnc_write_u16(vs, 1); /* number of rects */ + vnc_framebuffer_update(vs, 0, 0, ds->width, ds->height, 0x574D5669); + pixel_format_message(vs); + vnc_flush(vs); + } else { + if (vs->pix_bpp == 4 && vs->depth == 4 && + host_big_endian_flag == vs->pix_big_endian && + vs->client_red_max == 0xff && vs->client_green_max == 0xff && vs->client_blue_max == 0xff && + vs->client_red_shift == 16 && vs->client_green_shift == 8 && vs->client_blue_shift == 0) { + vs->write_pixels = vnc_write_pixels_copy; + vs->send_hextile_tile = send_hextile_tile_32; + } else if (vs->pix_bpp == 2 && vs->depth == 2 && + host_big_endian_flag == vs->pix_big_endian && + vs->client_red_max == 31 && vs->client_green_max == 63 && vs->client_blue_max == 31 && + vs->client_red_shift == 11 && vs->client_green_shift == 5 && vs->client_blue_shift == 0) { + vs->write_pixels = vnc_write_pixels_copy; + vs->send_hextile_tile = send_hextile_tile_16; + } else if (vs->pix_bpp == 1 && vs->depth == 1 && + host_big_endian_flag == vs->pix_big_endian && + vs->client_red_max == 7 && vs->client_green_max == 7 && vs->client_blue_max == 3 && + vs->client_red_shift == 5 && vs->client_green_shift == 2 && vs->client_blue_shift == 0) { + vs->write_pixels = vnc_write_pixels_copy; + vs->send_hextile_tile = send_hextile_tile_8; + } else { + if (vs->depth == 4) { + vs->send_hextile_tile = send_hextile_tile_generic_32; + } else if (vs->depth == 2) { + vs->send_hextile_tile = send_hextile_tile_generic_16; + } else { + vs->send_hextile_tile = send_hextile_tile_generic_8; + } + vs->write_pixels = vnc_write_pixels_generic; + } + } +} + +static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) { int i; uint16_t limit; @@ -1041,10 +1503,13 @@ static int protocol_client_msg(VncState *vs, char *data, size_t len) if (len == 1) return 4; - if (len == 4) - return 4 + (read_u16(data, 2) * 4); + if (len == 4) { + limit = read_u16(data, 2); + if (limit > 0) + return 4 + (limit * 4); + } else + limit = read_u16(data, 2); - limit = read_u16(data, 2); for (i = 0; i < limit; i++) { int32_t val = read_s32(data, 4 + (i * 4)); memcpy(data + 4 + (i * 4), &val, sizeof(val)); @@ -1076,69 +1541,95 @@ static int protocol_client_msg(VncState *vs, char *data, size_t len) if (len == 1) return 8; - if (len == 8) - return 8 + read_u32(data, 4); + if (len == 8) { + uint32_t dlen = read_u32(data, 4); + if (dlen > 0) + return 8 + dlen; + } client_cut_text(vs, read_u32(data, 4), data + 8); break; + case 255: + if (len == 1) + return 2; + + switch (read_u8(data, 1)) { + case 0: + if (len == 2) + return 12; + + ext_key_event(vs, read_u16(data, 2), + read_u32(data, 4), read_u32(data, 8)); + break; + case 1: + if (len == 2) + return 4; + + switch (read_u16 (data, 2)) { + case 0: + audio_add(vs); + break; + case 1: + audio_del(vs); + break; + case 2: + 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; + default: + printf("Invalid audio format %d\n", read_u8(data, 4)); + vnc_client_error(vs); + break; + } + vs->as.nchannels = read_u8(data, 5); + if (vs->as.nchannels != 1 && vs->as.nchannels != 2) { + printf("Invalid audio channel coount %d\n", + read_u8(data, 5)); + vnc_client_error(vs); + break; + } + vs->as.freq = read_u32(data, 6); + break; + default: + printf ("Invalid audio message %d\n", read_u8(data, 4)); + vnc_client_error(vs); + break; + } + break; + + default: + printf("Msg: %d\n", read_u16(data, 0)); + vnc_client_error(vs); + break; + } + break; default: printf("Msg: %d\n", data[0]); vnc_client_error(vs); break; } - + vnc_read_when(vs, protocol_client_msg, 1); return 0; } -static int protocol_client_init(VncState *vs, char *data, size_t len) +static int protocol_client_init(VncState *vs, uint8_t *data, size_t len) { - char pad[3] = { 0, 0, 0 }; char buf[1024]; int size; - vs->width = vs->ds->width; - vs->height = vs->ds->height; - vnc_write_u16(vs, vs->ds->width); - vnc_write_u16(vs, vs->ds->height); + vs->width = ds_get_width(vs->ds); + vs->height = ds_get_height(vs->ds); + vnc_write_u16(vs, ds_get_width(vs->ds)); + vnc_write_u16(vs, ds_get_height(vs->ds)); - vnc_write_u8(vs, vs->depth * 8); /* bits-per-pixel */ - vnc_write_u8(vs, vs->depth * 8); /* depth */ -#ifdef WORDS_BIGENDIAN - vnc_write_u8(vs, 1); /* big-endian-flag */ -#else - vnc_write_u8(vs, 0); /* big-endian-flag */ -#endif - vnc_write_u8(vs, 1); /* true-color-flag */ - if (vs->depth == 4) { - vnc_write_u16(vs, 0xFF); /* red-max */ - vnc_write_u16(vs, 0xFF); /* green-max */ - vnc_write_u16(vs, 0xFF); /* blue-max */ - vnc_write_u8(vs, 16); /* red-shift */ - vnc_write_u8(vs, 8); /* green-shift */ - vnc_write_u8(vs, 0); /* blue-shift */ - vs->send_hextile_tile = send_hextile_tile_32; - } else if (vs->depth == 2) { - vnc_write_u16(vs, 31); /* red-max */ - vnc_write_u16(vs, 63); /* green-max */ - vnc_write_u16(vs, 31); /* blue-max */ - vnc_write_u8(vs, 11); /* red-shift */ - vnc_write_u8(vs, 5); /* green-shift */ - vnc_write_u8(vs, 0); /* blue-shift */ - vs->send_hextile_tile = send_hextile_tile_16; - } else if (vs->depth == 1) { - /* XXX: change QEMU pixel 8 bit pixel format to match the VNC one ? */ - vnc_write_u16(vs, 7); /* red-max */ - vnc_write_u16(vs, 7); /* green-max */ - vnc_write_u16(vs, 3); /* blue-max */ - vnc_write_u8(vs, 5); /* red-shift */ - vnc_write_u8(vs, 2); /* green-shift */ - vnc_write_u8(vs, 0); /* blue-shift */ - vs->send_hextile_tile = send_hextile_tile_8; - } - vs->write_pixels = vnc_write_pixels_copy; - - vnc_write(vs, pad, 3); /* padding */ + pixel_format_message(vs); if (qemu_name) size = snprintf(buf, sizeof(buf), "QEMU (%s)", qemu_name); @@ -1164,11 +1655,11 @@ static void make_challenge(VncState *vs) vs->challenge[i] = (int) (256.0*rand()/(RAND_MAX+1.0)); } -static int protocol_client_auth_vnc(VncState *vs, char *data, size_t len) +static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len) { - char response[VNC_AUTH_CHALLENGE_SIZE]; + unsigned char response[VNC_AUTH_CHALLENGE_SIZE]; int i, j, pwlen; - char key[8]; + unsigned char key[8]; if (!vs->password || !vs->password[0]) { VNC_DEBUG("No password configured on server"); @@ -1225,7 +1716,417 @@ static int start_auth_vnc(VncState *vs) return 0; } -static int protocol_client_auth(VncState *vs, char *data, size_t len) + +#ifdef CONFIG_VNC_TLS +#define DH_BITS 1024 +static gnutls_dh_params_t dh_params; + +static int vnc_tls_initialize(void) +{ + static int tlsinitialized = 0; + + if (tlsinitialized) + return 1; + + if (gnutls_global_init () < 0) + return 0; + + /* XXX ought to re-generate diffie-hellmen params periodically */ + if (gnutls_dh_params_init (&dh_params) < 0) + return 0; + if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0) + return 0; + +#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 + gnutls_global_set_log_level(10); + gnutls_global_set_log_function(vnc_debug_gnutls_log); +#endif + + tlsinitialized = 1; + + return 1; +} + +static gnutls_anon_server_credentials vnc_tls_initialize_anon_cred(void) +{ + gnutls_anon_server_credentials anon_cred; + int ret; + + if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) { + VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); + return NULL; + } + + gnutls_anon_set_server_dh_params(anon_cred, dh_params); + + return anon_cred; +} + + +static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(VncState *vs) +{ + gnutls_certificate_credentials_t x509_cred; + int ret; + + if (!vs->x509cacert) { + VNC_DEBUG("No CA x509 certificate specified\n"); + return NULL; + } + if (!vs->x509cert) { + VNC_DEBUG("No server x509 certificate specified\n"); + return NULL; + } + if (!vs->x509key) { + VNC_DEBUG("No server private key specified\n"); + return NULL; + } + + if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) { + VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); + return NULL; + } + if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred, + vs->x509cacert, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + + if ((ret = gnutls_certificate_set_x509_key_file (x509_cred, + vs->x509cert, + vs->x509key, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + + if (vs->x509cacrl) { + if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, + vs->x509cacrl, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + } + + gnutls_certificate_set_dh_params (x509_cred, dh_params); + + return x509_cred; +} + +static int vnc_validate_certificate(struct VncState *vs) +{ + int ret; + unsigned int status; + const gnutls_datum_t *certs; + unsigned int nCerts, i; + time_t now; + + VNC_DEBUG("Validating client certificate\n"); + if ((ret = gnutls_certificate_verify_peers2 (vs->tls_session, &status)) < 0) { + VNC_DEBUG("Verify failed %s\n", gnutls_strerror(ret)); + return -1; + } + + if ((now = time(NULL)) == ((time_t)-1)) { + return -1; + } + + if (status != 0) { + if (status & GNUTLS_CERT_INVALID) + VNC_DEBUG("The certificate is not trusted.\n"); + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + VNC_DEBUG("The certificate hasn't got a known issuer.\n"); + + if (status & GNUTLS_CERT_REVOKED) + VNC_DEBUG("The certificate has been revoked.\n"); + + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) + VNC_DEBUG("The certificate uses an insecure algorithm\n"); + + return -1; + } else { + VNC_DEBUG("Certificate is valid!\n"); + } + + /* Only support x509 for now */ + if (gnutls_certificate_type_get(vs->tls_session) != GNUTLS_CRT_X509) + return -1; + + if (!(certs = gnutls_certificate_get_peers(vs->tls_session, &nCerts))) + return -1; + + for (i = 0 ; i < nCerts ; i++) { + gnutls_x509_crt_t cert; + VNC_DEBUG ("Checking certificate chain %d\n", i); + if (gnutls_x509_crt_init (&cert) < 0) + return -1; + + if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) { + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_expiration_time (cert) < now) { + VNC_DEBUG("The certificate has expired\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_activation_time (cert) > now) { + VNC_DEBUG("The certificate is not yet activated\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_activation_time (cert) > now) { + VNC_DEBUG("The certificate is not yet activated\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + gnutls_x509_crt_deinit (cert); + } + + return 0; +} + + +static int start_auth_vencrypt_subauth(VncState *vs) +{ + switch (vs->subauth) { + case VNC_AUTH_VENCRYPT_TLSNONE: + case VNC_AUTH_VENCRYPT_X509NONE: + VNC_DEBUG("Accept TLS auth none\n"); + vnc_write_u32(vs, 0); /* Accept auth completion */ + vnc_read_when(vs, protocol_client_init, 1); + break; + + case VNC_AUTH_VENCRYPT_TLSVNC: + case VNC_AUTH_VENCRYPT_X509VNC: + VNC_DEBUG("Start TLS auth VNC\n"); + return start_auth_vnc(vs); + + default: /* Should not be possible, but just in case */ + VNC_DEBUG("Reject auth %d\n", vs->auth); + vnc_write_u8(vs, 1); + if (vs->minor >= 8) { + static const char err[] = "Unsupported authentication type"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_client_error(vs); + } + + return 0; +} + +static void vnc_handshake_io(void *opaque); + +static int vnc_continue_handshake(struct VncState *vs) { + int ret; + + if ((ret = gnutls_handshake(vs->tls_session)) < 0) { + if (!gnutls_error_is_fatal(ret)) { + VNC_DEBUG("Handshake interrupted (blocking)\n"); + if (!gnutls_record_get_direction(vs->tls_session)) + qemu_set_fd_handler(vs->csock, vnc_handshake_io, NULL, vs); + else + qemu_set_fd_handler(vs->csock, NULL, vnc_handshake_io, vs); + return 0; + } + VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret)); + vnc_client_error(vs); + return -1; + } + + if (vs->x509verify) { + if (vnc_validate_certificate(vs) < 0) { + VNC_DEBUG("Client verification failed\n"); + vnc_client_error(vs); + return -1; + } else { + VNC_DEBUG("Client verification passed\n"); + } + } + + VNC_DEBUG("Handshake done, switching to TLS data mode\n"); + vs->wiremode = VNC_WIREMODE_TLS; + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs); + + return start_auth_vencrypt_subauth(vs); +} + +static void vnc_handshake_io(void *opaque) { + struct VncState *vs = (struct VncState *)opaque; + + VNC_DEBUG("Handshake IO continue\n"); + vnc_continue_handshake(vs); +} + +#define NEED_X509_AUTH(vs) \ + ((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ + (vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ + (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN) + + +static int vnc_start_tls(struct VncState *vs) { + static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; + static const int protocol_priority[]= { GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0 }; + static const int kx_anon[] = {GNUTLS_KX_ANON_DH, 0}; + static const int kx_x509[] = {GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0}; + + VNC_DEBUG("Do TLS setup\n"); + if (vnc_tls_initialize() < 0) { + VNC_DEBUG("Failed to init TLS\n"); + vnc_client_error(vs); + return -1; + } + if (vs->tls_session == NULL) { + if (gnutls_init(&vs->tls_session, GNUTLS_SERVER) < 0) { + vnc_client_error(vs); + return -1; + } + + if (gnutls_set_default_priority(vs->tls_session) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_kx_set_priority(vs->tls_session, NEED_X509_AUTH(vs) ? kx_x509 : kx_anon) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_certificate_type_set_priority(vs->tls_session, cert_type_priority) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_protocol_set_priority(vs->tls_session, protocol_priority) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + + if (NEED_X509_AUTH(vs)) { + gnutls_certificate_server_credentials x509_cred = vnc_tls_initialize_x509_cred(vs); + if (!x509_cred) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + if (gnutls_credentials_set(vs->tls_session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + gnutls_certificate_free_credentials(x509_cred); + vnc_client_error(vs); + return -1; + } + if (vs->x509verify) { + VNC_DEBUG("Requesting a client certificate\n"); + gnutls_certificate_server_set_request (vs->tls_session, GNUTLS_CERT_REQUEST); + } + + } else { + gnutls_anon_server_credentials anon_cred = vnc_tls_initialize_anon_cred(); + if (!anon_cred) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + if (gnutls_credentials_set(vs->tls_session, GNUTLS_CRD_ANON, anon_cred) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + gnutls_anon_free_server_credentials(anon_cred); + vnc_client_error(vs); + return -1; + } + } + + gnutls_transport_set_ptr(vs->tls_session, (gnutls_transport_ptr_t)vs); + gnutls_transport_set_push_function(vs->tls_session, vnc_tls_push); + gnutls_transport_set_pull_function(vs->tls_session, vnc_tls_pull); + } + + VNC_DEBUG("Start TLS handshake process\n"); + return vnc_continue_handshake(vs); +} + +static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len) +{ + int auth = read_u32(data, 0); + + if (auth != vs->subauth) { + VNC_DEBUG("Rejecting auth %d\n", auth); + vnc_write_u8(vs, 0); /* Reject auth */ + vnc_flush(vs); + vnc_client_error(vs); + } else { + VNC_DEBUG("Accepting auth %d, starting handshake\n", auth); + vnc_write_u8(vs, 1); /* Accept auth */ + vnc_flush(vs); + + if (vnc_start_tls(vs) < 0) { + VNC_DEBUG("Failed to complete TLS\n"); + return 0; + } + + if (vs->wiremode == VNC_WIREMODE_TLS) { + VNC_DEBUG("Starting VeNCrypt subauth\n"); + return start_auth_vencrypt_subauth(vs); + } else { + VNC_DEBUG("TLS handshake blocked\n"); + return 0; + } + } + return 0; +} + +static int protocol_client_vencrypt_init(VncState *vs, uint8_t *data, size_t len) +{ + if (data[0] != 0 || + data[1] != 2) { + VNC_DEBUG("Unsupported VeNCrypt protocol %d.%d\n", (int)data[0], (int)data[1]); + vnc_write_u8(vs, 1); /* Reject version */ + vnc_flush(vs); + vnc_client_error(vs); + } else { + VNC_DEBUG("Sending allowed auth %d\n", vs->subauth); + vnc_write_u8(vs, 0); /* Accept version */ + vnc_write_u8(vs, 1); /* Number of sub-auths */ + vnc_write_u32(vs, vs->subauth); /* The supported auth */ + vnc_flush(vs); + vnc_read_when(vs, protocol_client_vencrypt_auth, 4); + } + return 0; +} + +static int start_auth_vencrypt(VncState *vs) +{ + /* Send VeNCrypt version 0.2 */ + vnc_write_u8(vs, 0); + vnc_write_u8(vs, 2); + + vnc_read_when(vs, protocol_client_vencrypt_init, 2); + return 0; +} +#endif /* CONFIG_VNC_TLS */ + +static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len) { /* We only advertise 1 auth scheme at a time, so client * must pick the one we sent. Verify this */ @@ -1243,7 +2144,10 @@ static int protocol_client_auth(VncState *vs, char *data, size_t len) switch (vs->auth) { case VNC_AUTH_NONE: VNC_DEBUG("Accept auth none\n"); - vnc_write_u32(vs, 0); /* Accept auth completion */ + if (vs->minor >= 8) { + vnc_write_u32(vs, 0); /* Accept auth completion */ + vnc_flush(vs); + } vnc_read_when(vs, protocol_client_init, 1); break; @@ -1251,6 +2155,12 @@ static int protocol_client_auth(VncState *vs, char *data, size_t len) VNC_DEBUG("Start VNC auth\n"); return start_auth_vnc(vs); +#ifdef CONFIG_VNC_TLS + case VNC_AUTH_VENCRYPT: + VNC_DEBUG("Accept VeNCrypt auth\n");; + return start_auth_vencrypt(vs); +#endif /* CONFIG_VNC_TLS */ + default: /* Should not be possible, but just in case */ VNC_DEBUG("Reject auth %d\n", vs->auth); vnc_write_u8(vs, 1); @@ -1265,7 +2175,7 @@ static int protocol_client_auth(VncState *vs, char *data, size_t len) return 0; } -static int protocol_version(VncState *vs, char *version, size_t len) +static int protocol_version(VncState *vs, uint8_t *version, size_t len) { char local[13]; @@ -1280,6 +2190,7 @@ static int protocol_version(VncState *vs, char *version, size_t len) VNC_DEBUG("Client request protocol version %d.%d\n", vs->major, vs->minor); if (vs->major != 3 || (vs->minor != 3 && + vs->minor != 4 && vs->minor != 5 && vs->minor != 7 && vs->minor != 8)) { @@ -1289,10 +2200,10 @@ static int protocol_version(VncState *vs, char *version, size_t len) vnc_client_error(vs); return 0; } - /* Some broken client report v3.5 which spec requires to be treated + /* Some broken clients report v3.4 or v3.5, which spec requires to be treated * as equivalent to v3.3 by servers */ - if (vs->minor == 5) + if (vs->minor == 4 || vs->minor == 5) vs->minor = 3; if (vs->minor == 3) { @@ -1323,29 +2234,39 @@ static int protocol_version(VncState *vs, char *version, size_t len) return 0; } +static void vnc_connect(VncState *vs) +{ + VNC_DEBUG("New client on socket %d\n", vs->csock); + vs->ds->idle = 0; + socket_set_nonblock(vs->csock); + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); + vnc_write(vs, "RFB 003.008\n", 12); + vnc_flush(vs); + vnc_read_when(vs, protocol_version, 12); + memset(vs->old_data, 0, ds_get_linesize(vs->ds) * ds_get_height(vs->ds)); + memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); + vs->has_resize = 0; + vs->has_hextile = 0; + vs->ds->dpy_copy = NULL; + vnc_update_client(vs); + reset_keys(vs); +} + static void vnc_listen_read(void *opaque) { VncState *vs = opaque; struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); + /* Catch-up */ + vga_hw_update(); + vs->csock = accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); if (vs->csock != -1) { - socket_set_nonblock(vs->csock); - qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, opaque); - vnc_write(vs, "RFB 003.008\n", 12); - vnc_flush(vs); - vnc_read_when(vs, protocol_version, 12); - memset(vs->old_data, 0, vs->ds->linesize * vs->ds->height); - memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); - vs->has_resize = 0; - vs->has_hextile = 0; - vs->ds->dpy_copy = NULL; + vnc_connect(vs); } } -extern int parse_host_port(struct sockaddr_in *saddr, const char *str); - void vnc_display_init(DisplayState *ds) { VncState *vs; @@ -1355,35 +2276,99 @@ void vnc_display_init(DisplayState *ds) exit(1); ds->opaque = vs; + ds->idle = 1; vnc_state = vs; vs->display = NULL; vs->password = NULL; vs->lsock = -1; vs->csock = -1; - vs->depth = 4; vs->last_x = -1; vs->last_y = -1; vs->ds = ds; - if (!keyboard_layout) - keyboard_layout = "en-us"; + if (keyboard_layout) + vs->kbd_layout = init_keyboard_layout(keyboard_layout); + else + vs->kbd_layout = init_keyboard_layout("en-us"); - vs->kbd_layout = init_keyboard_layout(keyboard_layout); if (!vs->kbd_layout) exit(1); + vs->timer = qemu_new_timer(rt_clock, vnc_update_client, vs); + vs->ds->data = NULL; vs->ds->dpy_update = vnc_dpy_update; vs->ds->dpy_resize = vnc_dpy_resize; - vs->ds->dpy_refresh = vnc_dpy_refresh; - - memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); + vs->ds->dpy_refresh = NULL; + vnc_colordepth(vs->ds, 32); vnc_dpy_resize(vs->ds, 640, 400); + + vs->as.freq = 44100; + vs->as.nchannels = 2; + vs->as.fmt = AUD_FMT_S16; + vs->as.endianness = 0; +} + +#ifdef CONFIG_VNC_TLS +static int vnc_set_x509_credential(VncState *vs, + const char *certdir, + const char *filename, + char **cred, + int ignoreMissing) +{ + struct stat sb; + + if (*cred) { + qemu_free(*cred); + *cred = NULL; + } + + if (!(*cred = qemu_malloc(strlen(certdir) + strlen(filename) + 2))) + return -1; + + strcpy(*cred, certdir); + strcat(*cred, "/"); + strcat(*cred, filename); + + VNC_DEBUG("Check %s\n", *cred); + if (stat(*cred, &sb) < 0) { + qemu_free(*cred); + *cred = NULL; + if (ignoreMissing && errno == ENOENT) + return 0; + return -1; + } + + return 0; } +static int vnc_set_x509_credential_dir(VncState *vs, + const char *certdir) +{ + if (vnc_set_x509_credential(vs, certdir, X509_CA_CERT_FILE, &vs->x509cacert, 0) < 0) + goto cleanup; + if (vnc_set_x509_credential(vs, certdir, X509_CA_CRL_FILE, &vs->x509cacrl, 1) < 0) + goto cleanup; + if (vnc_set_x509_credential(vs, certdir, X509_SERVER_CERT_FILE, &vs->x509cert, 0) < 0) + goto cleanup; + if (vnc_set_x509_credential(vs, certdir, X509_SERVER_KEY_FILE, &vs->x509key, 0) < 0) + goto cleanup; + + return 0; + + cleanup: + qemu_free(vs->x509cacert); + qemu_free(vs->x509cacrl); + qemu_free(vs->x509cert); + qemu_free(vs->x509key); + vs->x509cacert = vs->x509cacrl = vs->x509cert = vs->x509key = NULL; + return -1; +} +#endif /* CONFIG_VNC_TLS */ + void vnc_display_close(DisplayState *ds) { VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; @@ -1404,8 +2389,20 @@ void vnc_display_close(DisplayState *ds) buffer_reset(&vs->input); buffer_reset(&vs->output); vs->need_update = 0; +#ifdef CONFIG_VNC_TLS + if (vs->tls_session) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + } + vs->wiremode = VNC_WIREMODE_CLEAR; +#endif /* CONFIG_VNC_TLS */ } vs->auth = VNC_AUTH_INVALID; +#ifdef CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; + vs->x509verify = 0; +#endif + audio_del(vs); } int vnc_display_password(DisplayState *ds, const char *password) @@ -1426,17 +2423,14 @@ int vnc_display_password(DisplayState *ds, const char *password) int vnc_display_open(DisplayState *ds, const char *display) { - struct sockaddr *addr; - struct sockaddr_in iaddr; -#ifndef _WIN32 - struct sockaddr_un uaddr; -#endif - int reuse_addr, ret; - socklen_t addrlen; - const char *p; VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; const char *options; int password = 0; + int reverse = 0; + int to_port = 0; +#ifdef CONFIG_VNC_TLS + int tls = 0, x509 = 0; +#endif vnc_display_close(ds); if (strcmp(display, "none") == 0) @@ -1448,87 +2442,122 @@ int vnc_display_open(DisplayState *ds, const char *display) options = display; while ((options = strchr(options, ','))) { options++; - if (strncmp(options, "password", 8) == 0) + if (strncmp(options, "password", 8) == 0) { password = 1; /* Require password auth */ + } else if (strncmp(options, "reverse", 7) == 0) { + reverse = 1; + } else if (strncmp(options, "to=", 3) == 0) { + to_port = atoi(options+3) + 5900; +#ifdef CONFIG_VNC_TLS + } else if (strncmp(options, "tls", 3) == 0) { + tls = 1; /* Require TLS */ + } else if (strncmp(options, "x509", 4) == 0) { + char *start, *end; + x509 = 1; /* Require x509 certificates */ + if (strncmp(options, "x509verify", 10) == 0) + vs->x509verify = 1; /* ...and verify client certs */ + + /* Now check for 'x509=/some/path' postfix + * and use that to setup x509 certificate/key paths */ + start = strchr(options, '='); + end = strchr(options, ','); + if (start && (!end || (start < end))) { + int len = end ? end-(start+1) : strlen(start+1); + char *path = qemu_strndup(start + 1, len); + + VNC_DEBUG("Trying certificate path '%s'\n", path); + if (vnc_set_x509_credential_dir(vs, path) < 0) { + fprintf(stderr, "Failed to find x509 certificates/keys in %s\n", path); + qemu_free(path); + qemu_free(vs->display); + vs->display = NULL; + return -1; + } + qemu_free(path); + } else { + fprintf(stderr, "No certificate path provided\n"); + qemu_free(vs->display); + vs->display = NULL; + return -1; + } +#endif + } } if (password) { - VNC_DEBUG("Initializing VNC server with password auth\n"); - vs->auth = VNC_AUTH_VNC; - } else { - VNC_DEBUG("Initializing VNC server with no auth\n"); - vs->auth = VNC_AUTH_NONE; - } -#ifndef _WIN32 - if (strstart(display, "unix:", &p)) { - addr = (struct sockaddr *)&uaddr; - addrlen = sizeof(uaddr); - - vs->lsock = socket(PF_UNIX, SOCK_STREAM, 0); - if (vs->lsock == -1) { - fprintf(stderr, "Could not create socket\n"); - free(vs->display); - vs->display = NULL; - return -1; - } - - uaddr.sun_family = AF_UNIX; - memset(uaddr.sun_path, 0, 108); - snprintf(uaddr.sun_path, 108, "%s", p); - - unlink(uaddr.sun_path); - } else +#ifdef CONFIG_VNC_TLS + if (tls) { + vs->auth = VNC_AUTH_VENCRYPT; + if (x509) { + VNC_DEBUG("Initializing VNC server with x509 password auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_X509VNC; + } else { + VNC_DEBUG("Initializing VNC server with TLS password auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC; + } + } else { #endif - { - addr = (struct sockaddr *)&iaddr; - addrlen = sizeof(iaddr); - - if (parse_host_port(&iaddr, display) < 0) { - fprintf(stderr, "Could not parse VNC address\n"); - free(vs->display); - vs->display = NULL; - return -1; - } - - iaddr.sin_port = htons(ntohs(iaddr.sin_port) + 5900); - - vs->lsock = socket(PF_INET, SOCK_STREAM, 0); - if (vs->lsock == -1) { - fprintf(stderr, "Could not create socket\n"); - free(vs->display); - vs->display = NULL; - return -1; + VNC_DEBUG("Initializing VNC server with password auth\n"); + vs->auth = VNC_AUTH_VNC; +#ifdef CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; } - - reuse_addr = 1; - ret = setsockopt(vs->lsock, SOL_SOCKET, SO_REUSEADDR, - (const char *)&reuse_addr, sizeof(reuse_addr)); - if (ret == -1) { - fprintf(stderr, "setsockopt() failed\n"); - close(vs->lsock); - vs->lsock = -1; - free(vs->display); - vs->display = NULL; - return -1; +#endif + } else { +#ifdef CONFIG_VNC_TLS + if (tls) { + vs->auth = VNC_AUTH_VENCRYPT; + if (x509) { + VNC_DEBUG("Initializing VNC server with x509 no auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_X509NONE; + } else { + VNC_DEBUG("Initializing VNC server with TLS no auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE; + } + } else { +#endif + VNC_DEBUG("Initializing VNC server with no auth\n"); + vs->auth = VNC_AUTH_NONE; +#ifdef CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; } +#endif } - if (bind(vs->lsock, addr, addrlen) == -1) { - fprintf(stderr, "bind() failed\n"); - close(vs->lsock); - vs->lsock = -1; - free(vs->display); - vs->display = NULL; - return -1; - } + 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) { + free(vs->display); + vs->display = NULL; + return -1; + } else { + vs->csock = vs->lsock; + vs->lsock = -1; + vnc_connect(vs); + } + return 0; - if (listen(vs->lsock, 1) == -1) { - fprintf(stderr, "listen() failed\n"); - close(vs->lsock); - vs->lsock = -1; - free(vs->display); - vs->display = NULL; - return -1; + } else { + /* listen for connects */ + char *dpy; + dpy = qemu_malloc(256); + if (strncmp(display, "unix:", 5) == 0) { + pstrcpy(dpy, 256, "unix:"); + vs->lsock = unix_listen(display+5, dpy+5, 256-5); + } else { + vs->lsock = inet_listen(display, dpy, 256, SOCK_STREAM, 5900); + } + if (-1 == vs->lsock) { + free(dpy); + return -1; + } else { + free(vs->display); + vs->display = dpy; + } } return qemu_set_fd_handler2(vs->lsock, vnc_listen_poll, vnc_listen_read, NULL, vs);