X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/2f9606b3736c3be4dbd606c46525c7b770ced119..7a84cb23c077488b0e1926b3e909ea128a80dc58:/vnc.c diff --git a/vnc.c b/vnc.c index 074238790b..32c467880b 100644 --- a/vnc.c +++ b/vnc.c @@ -28,8 +28,11 @@ #include "sysemu.h" #include "qemu_socket.h" #include "qemu-timer.h" +#include "acl.h" -#define VNC_REFRESH_INTERVAL (1000 / 30) +#define VNC_REFRESH_INTERVAL_BASE 30 +#define VNC_REFRESH_INTERVAL_INC 50 +#define VNC_REFRESH_INTERVAL_MAX 2000 #include "vnc_keysym.h" #include "d3des.h" @@ -52,6 +55,7 @@ static char *addr_to_string(const char *format, char host[NI_MAXHOST]; char serv[NI_MAXSERV]; int err; + size_t addrlen; if ((err = getnameinfo((struct sockaddr *)sa, salen, host, sizeof(host), @@ -62,8 +66,12 @@ static char *addr_to_string(const char *format, return NULL; } - if (asprintf(&addr, format, host, serv) < 0) - return NULL; + /* Enough for the existing format + the 2 vars we're + * substituting in. */ + addrlen = strlen(format) + strlen(host) + strlen(serv); + addr = qemu_malloc(addrlen + 1); + snprintf(addr, addrlen, format, host, serv); + addr[addrlen] = '\0'; return addr; } @@ -80,7 +88,6 @@ char *vnc_socket_local_addr(const char *format, int fd) { return addr_to_string(format, &sa, salen); } - char *vnc_socket_remote_addr(const char *format, int fd) { struct sockaddr_storage sa; socklen_t salen; @@ -127,10 +134,10 @@ static const char *vnc_auth_name(VncDisplay *vd) { return "vencrypt+x509+vnc"; case VNC_AUTH_VENCRYPT_X509PLAIN: return "vencrypt+x509+plain"; - case VNC_AUTH_VENCRYPT_TLSSASL: - return "vencrypt+tls+sasl"; - case VNC_AUTH_VENCRYPT_X509SASL: - return "vencrypt+x509+sasl"; + case VNC_AUTH_VENCRYPT_TLSSASL: + return "vencrypt+tls+sasl"; + case VNC_AUTH_VENCRYPT_X509SASL: + return "vencrypt+x509+sasl"; default: return "vencrypt"; } @@ -138,13 +145,11 @@ static const char *vnc_auth_name(VncDisplay *vd) { return "vencrypt"; #endif case VNC_AUTH_SASL: - return "sasl"; + return "sasl"; } return "unknown"; } -#define VNC_SOCKET_FORMAT_PRETTY "local %s:%s" - static void do_info_vnc_client(Monitor *mon, VncState *client) { char *clientAddr = @@ -156,6 +161,21 @@ static void do_info_vnc_client(Monitor *mon, VncState *client) monitor_printf(mon, "Client:\n"); monitor_printf(mon, "%s", clientAddr); free(clientAddr); + +#ifdef CONFIG_VNC_TLS + if (client->tls.session && + client->tls.dname) + monitor_printf(mon, " x509 dname: %s\n", client->tls.dname); + else + monitor_printf(mon, " x509 dname: none\n"); +#endif +#ifdef CONFIG_VNC_SASL + if (client->sasl.conn && + client->sasl.username) + monitor_printf(mon, " username: %s\n", client->sasl.username); + else + monitor_printf(mon, " username: none\n"); +#endif } void do_info_vnc(Monitor *mon) @@ -197,9 +217,18 @@ static inline uint32_t vnc_has_feature(VncState *vs, int feature) { 3) resolutions > 1024 */ -static void vnc_update_client(void *opaque); +static int vnc_update_client(VncState *vs, int has_dirty); +static void vnc_disconnect_start(VncState *vs); +static void vnc_disconnect_finish(VncState *vs); +static void vnc_init_timer(VncDisplay *vd); +static void vnc_remove_timer(VncDisplay *vd); static void vnc_colordepth(VncState *vs); +static void framebuffer_update_request(VncState *vs, int incremental, + int x_position, int y_position, + int w, int h); +static void vnc_refresh(void *opaque); +static int vnc_refresh_server_surface(VncDisplay *vd); static inline void vnc_set_bit(uint32_t *d, int k) { @@ -242,9 +271,11 @@ static inline int vnc_and_bits(const uint32_t *d1, const uint32_t *d2, return 0; } -static void vnc_update(VncState *vs, int x, int y, int w, int h) +static void vnc_dpy_update(DisplayState *ds, int x, int y, int w, int h) { int i; + VncDisplay *vd = ds->opaque; + struct VncSurface *s = &vd->guest; h += y; @@ -255,28 +286,18 @@ static void vnc_update(VncState *vs, int x, int y, int w, int h) w += (x % 16); x -= (x % 16); - x = MIN(x, vs->serverds.width); - y = MIN(y, vs->serverds.height); - w = MIN(x + w, vs->serverds.width) - x; - h = MIN(h, vs->serverds.height); + x = MIN(x, s->ds->width); + y = MIN(y, s->ds->height); + w = MIN(x + w, s->ds->width) - x; + h = MIN(h, s->ds->height); for (; y < h; y++) - for (i = 0; i < w; i += 16) - vnc_set_bit(vs->dirty_row[y], (x + i) / 16); -} - -static void vnc_dpy_update(DisplayState *ds, int x, int y, int w, int h) -{ - VncDisplay *vd = ds->opaque; - VncState *vs = vd->clients; - while (vs != NULL) { - vnc_update(vs, x, y, w, h); - vs = vs->next; - } + for (i = 0; i < w; i += 16) + vnc_set_bit(s->dirty[y], (x + i) / 16); } static void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, - int32_t encoding) + int32_t encoding) { vnc_write_u16(vs, x); vnc_write_u16(vs, y); @@ -289,12 +310,12 @@ static void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, void buffer_reserve(Buffer *buffer, size_t len) { if ((buffer->capacity - buffer->offset) < len) { - buffer->capacity += (len + 1024); - buffer->buffer = qemu_realloc(buffer->buffer, buffer->capacity); - if (buffer->buffer == NULL) { - fprintf(stderr, "vnc: out of memory\n"); - exit(1); - } + buffer->capacity += (len + 1024); + buffer->buffer = qemu_realloc(buffer->buffer, buffer->capacity); + if (buffer->buffer == NULL) { + fprintf(stderr, "vnc: out of memory\n"); + exit(1); + } } } @@ -310,7 +331,7 @@ uint8_t *buffer_end(Buffer *buffer) void buffer_reset(Buffer *buffer) { - buffer->offset = 0; + buffer->offset = 0; } void buffer_append(Buffer *buffer, const void *data, size_t len) @@ -319,46 +340,44 @@ void buffer_append(Buffer *buffer, const void *data, size_t len) buffer->offset += len; } -static void vnc_resize(VncState *vs) +static void vnc_dpy_resize(DisplayState *ds) { - DisplayState *ds = vs->ds; - int size_changed; + VncDisplay *vd = ds->opaque; + VncState *vs = vd->clients; - vs->old_data = qemu_realloc(vs->old_data, ds_get_linesize(ds) * ds_get_height(ds)); - - if (vs->old_data == NULL) { - fprintf(stderr, "vnc: memory allocation failed\n"); - exit(1); - } - - if (ds_get_bytes_per_pixel(ds) != vs->serverds.pf.bytes_per_pixel) + /* server surface */ + if (!vd->server) + vd->server = qemu_mallocz(sizeof(*vd->server)); + if (vd->server->data) + qemu_free(vd->server->data); + *(vd->server) = *(ds->surface); + vd->server->data = qemu_mallocz(vd->server->linesize * + vd->server->height); + + /* guest surface */ + if (!vd->guest.ds) + vd->guest.ds = qemu_mallocz(sizeof(*vd->guest.ds)); + if (ds_get_bytes_per_pixel(ds) != vd->guest.ds->pf.bytes_per_pixel) console_color_init(ds); - vnc_colordepth(vs); - size_changed = ds_get_width(ds) != vs->serverds.width || - ds_get_height(ds) != vs->serverds.height; - vs->serverds = *(ds->surface); - if (size_changed) { - if (vs->csock != -1 && vnc_has_feature(vs, VNC_FEATURE_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_get_width(ds), ds_get_height(ds), - VNC_ENCODING_DESKTOPRESIZE); - vnc_flush(vs); - } - } + size_changed = ds_get_width(ds) != vd->guest.ds->width || + ds_get_height(ds) != vd->guest.ds->height; + *(vd->guest.ds) = *(ds->surface); + memset(vd->guest.dirty, 0xFF, sizeof(vd->guest.dirty)); - memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); - memset(vs->old_data, 42, ds_get_linesize(vs->ds) * ds_get_height(vs->ds)); -} - -static void vnc_dpy_resize(DisplayState *ds) -{ - VncDisplay *vd = ds->opaque; - VncState *vs = vd->clients; while (vs != NULL) { - vnc_resize(vs); + vnc_colordepth(vs); + if (size_changed) { + if (vs->csock != -1 && vnc_has_feature(vs, VNC_FEATURE_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_get_width(ds), ds_get_height(ds), + VNC_ENCODING_DESKTOPRESIZE); + vnc_flush(vs); + } + } + memset(vs->dirty, 0xFF, sizeof(vs->dirty)); vs = vs->next; } } @@ -373,13 +392,14 @@ static void vnc_write_pixels_copy(VncState *vs, void *pixels, int size) static void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v) { uint8_t r, g, b; - - r = ((((v & vs->serverds.pf.rmask) >> vs->serverds.pf.rshift) << vs->clientds.pf.rbits) >> - vs->serverds.pf.rbits); - g = ((((v & vs->serverds.pf.gmask) >> vs->serverds.pf.gshift) << vs->clientds.pf.gbits) >> - vs->serverds.pf.gbits); - b = ((((v & vs->serverds.pf.bmask) >> vs->serverds.pf.bshift) << vs->clientds.pf.bbits) >> - vs->serverds.pf.bbits); + VncDisplay *vd = vs->vd; + + r = ((((v & vd->server->pf.rmask) >> vd->server->pf.rshift) << vs->clientds.pf.rbits) >> + vd->server->pf.rbits); + g = ((((v & vd->server->pf.gmask) >> vd->server->pf.gshift) << vs->clientds.pf.gbits) >> + vd->server->pf.gbits); + b = ((((v & vd->server->pf.bmask) >> vd->server->pf.bshift) << vs->clientds.pf.bbits) >> + vd->server->pf.bbits); v = (r << vs->clientds.pf.rshift) | (g << vs->clientds.pf.gshift) | (b << vs->clientds.pf.bshift); @@ -416,8 +436,9 @@ 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) { uint8_t buf[4]; + VncDisplay *vd = vs->vd; - if (vs->serverds.pf.bytes_per_pixel == 4) { + if (vd->server->pf.bytes_per_pixel == 4) { uint32_t *pixels = pixels1; int n, i; n = size >> 2; @@ -425,7 +446,7 @@ static void vnc_write_pixels_generic(VncState *vs, void *pixels1, int size) vnc_convert_pixel(vs, buf, pixels[i]); vnc_write(vs, buf, vs->clientds.pf.bytes_per_pixel); } - } else if (vs->serverds.pf.bytes_per_pixel == 2) { + } else if (vd->server->pf.bytes_per_pixel == 2) { uint16_t *pixels = pixels1; int n, i; n = size >> 1; @@ -433,7 +454,7 @@ static void vnc_write_pixels_generic(VncState *vs, void *pixels1, int size) vnc_convert_pixel(vs, buf, pixels[i]); vnc_write(vs, buf, vs->clientds.pf.bytes_per_pixel); } - } else if (vs->serverds.pf.bytes_per_pixel == 1) { + } else if (vd->server->pf.bytes_per_pixel == 1) { uint8_t *pixels = pixels1; int n, i; n = size; @@ -450,11 +471,12 @@ static void send_framebuffer_update_raw(VncState *vs, int x, int y, int w, int h { int i; uint8_t *row; + VncDisplay *vd = vs->vd; - row = ds_get_data(vs->ds) + y * ds_get_linesize(vs->ds) + x * ds_get_bytes_per_pixel(vs->ds); + row = vd->server->data + y * ds_get_linesize(vs->ds) + x * ds_get_bytes_per_pixel(vs->ds); for (i = 0; i < h; i++) { - vs->write_pixels(vs, row, w * ds_get_bytes_per_pixel(vs->ds)); - row += ds_get_linesize(vs->ds); + vs->write_pixels(vs, row, w * ds_get_bytes_per_pixel(vs->ds)); + row += ds_get_linesize(vs->ds); } } @@ -499,22 +521,42 @@ static void send_framebuffer_update_hextile(VncState *vs, int x, int y, int w, i int i, j; int has_fg, has_bg; uint8_t *last_fg, *last_bg; + VncDisplay *vd = vs->vd; - last_fg = (uint8_t *) qemu_malloc(vs->serverds.pf.bytes_per_pixel); - last_bg = (uint8_t *) qemu_malloc(vs->serverds.pf.bytes_per_pixel); + last_fg = (uint8_t *) qemu_malloc(vd->server->pf.bytes_per_pixel); + last_bg = (uint8_t *) qemu_malloc(vd->server->pf.bytes_per_pixel); has_fg = has_bg = 0; for (j = y; j < (y + h); j += 16) { - for (i = x; i < (x + w); i += 16) { + for (i = x; i < (x + w); i += 16) { vs->send_hextile_tile(vs, i, j, MIN(16, x + w - i), MIN(16, y + h - j), last_bg, last_fg, &has_bg, &has_fg); - } + } } free(last_fg); free(last_bg); } +#define ZALLOC_ALIGNMENT 16 + +static void *zalloc(void *x, unsigned items, unsigned size) +{ + void *p; + + size *= items; + size = (size + ZALLOC_ALIGNMENT - 1) & ~(ZALLOC_ALIGNMENT - 1); + + p = qemu_mallocz(size); + + return (p); +} + +static void zfree(void *x, void *addr) +{ + qemu_free(addr); +} + static void vnc_zlib_init(VncState *vs) { int i; @@ -549,8 +591,8 @@ static int vnc_zlib_stop(VncState *vs, int stream_id) VNC_DEBUG("VNC: initializing zlib stream %d\n", stream_id); VNC_DEBUG("VNC: opaque = %p | vs = %p\n", zstream->opaque, vs); - zstream->zalloc = Z_NULL; - zstream->zfree = Z_NULL; + zstream->zalloc = zalloc; + zstream->zfree = zfree; err = deflateInit2(zstream, vs->tight_compression, Z_DEFLATED, MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); @@ -614,24 +656,23 @@ static void send_framebuffer_update_zlib(VncState *vs, int x, int y, int w, int static void send_framebuffer_update(VncState *vs, int x, int y, int w, int h) { switch(vs->vnc_encoding) { - case VNC_ENCODING_ZLIB: - send_framebuffer_update_zlib(vs, x, y, w, h); - break; - case VNC_ENCODING_HEXTILE: - vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_HEXTILE); - send_framebuffer_update_hextile(vs, x, y, w, h); - break; - default: - vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW); - send_framebuffer_update_raw(vs, x, y, w, h); - break; + case VNC_ENCODING_ZLIB: + send_framebuffer_update_zlib(vs, x, y, w, h); + break; + case VNC_ENCODING_HEXTILE: + vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_HEXTILE); + send_framebuffer_update_hextile(vs, x, y, w, h); + break; + default: + vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW); + send_framebuffer_update_raw(vs, x, y, w, h); + break; } } static void vnc_copy(VncState *vs, int src_x, int src_y, int dst_x, int dst_y, int w, int h) { - vnc_update_client(vs); - + /* send bitblit op to the vnc client */ vnc_write_u8(vs, 0); /* msg id */ vnc_write_u8(vs, 0); vnc_write_u16(vs, 1); /* number of rects */ @@ -644,124 +685,155 @@ static void vnc_copy(VncState *vs, int src_x, int src_y, int dst_x, int dst_y, i static void vnc_dpy_copy(DisplayState *ds, int src_x, int src_y, int dst_x, int dst_y, int w, int h) { VncDisplay *vd = ds->opaque; - VncState *vs = vd->clients; - while (vs != NULL) { + VncState *vs, *vn; + uint8_t *src_row; + uint8_t *dst_row; + int i,x,y,pitch,depth,inc,w_lim,s; + int cmp_bytes; + + vnc_refresh_server_surface(vd); + for (vs = vd->clients; vs != NULL; vs = vn) { + vn = vs->next; + if (vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) { + vs->force_update = 1; + vnc_update_client(vs, 1); + /* vs might be free()ed here */ + } + } + + /* do bitblit op on the local surface too */ + pitch = ds_get_linesize(vd->ds); + depth = ds_get_bytes_per_pixel(vd->ds); + src_row = vd->server->data + pitch * src_y + depth * src_x; + dst_row = vd->server->data + pitch * dst_y + depth * dst_x; + y = dst_y; + inc = 1; + if (dst_y > src_y) { + /* copy backwards */ + src_row += pitch * (h-1); + dst_row += pitch * (h-1); + pitch = -pitch; + y = dst_y + h - 1; + inc = -1; + } + w_lim = w - (16 - (dst_x % 16)); + if (w_lim < 0) + w_lim = w; + else + w_lim = w - (w_lim % 16); + for (i = 0; i < h; i++) { + for (x = 0; x <= w_lim; + x += s, src_row += cmp_bytes, dst_row += cmp_bytes) { + if (x == w_lim) { + if ((s = w - w_lim) == 0) + break; + } else if (!x) { + s = (16 - (dst_x % 16)); + s = MIN(s, w_lim); + } else { + s = 16; + } + cmp_bytes = s * depth; + if (memcmp(src_row, dst_row, cmp_bytes) == 0) + continue; + memmove(dst_row, src_row, cmp_bytes); + vs = vd->clients; + while (vs != NULL) { + if (!vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) + vnc_set_bit(vs->dirty[y], ((x + dst_x) / 16)); + vs = vs->next; + } + } + src_row += pitch - w * depth; + dst_row += pitch - w * depth; + y += inc; + } + + for (vs = vd->clients; vs != NULL; vs = vs->next) { if (vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) vnc_copy(vs, src_x, src_y, dst_x, dst_y, w, h); - else /* TODO */ - vnc_update(vs, dst_x, dst_y, w, h); - vs = vs->next; } } -static int find_dirty_height(VncState *vs, int y, int last_x, int x) +static int find_and_clear_dirty_height(struct VncState *vs, + int y, int last_x, int x) { int h; + VncDisplay *vd = vs->vd; - for (h = 1; h < (vs->serverds.height - y); h++) { - int tmp_x; - if (!vnc_get_bit(vs->dirty_row[y + h], last_x)) - break; - for (tmp_x = last_x; tmp_x < x; tmp_x++) - vnc_clear_bit(vs->dirty_row[y + h], tmp_x); + for (h = 1; h < (vd->server->height - y); h++) { + int tmp_x; + if (!vnc_get_bit(vs->dirty[y + h], last_x)) + break; + for (tmp_x = last_x; tmp_x < x; tmp_x++) + vnc_clear_bit(vs->dirty[y + h], tmp_x); } return h; } -static void vnc_update_client(void *opaque) +static int vnc_update_client(VncState *vs, int has_dirty) { - VncState *vs = opaque; if (vs->need_update && vs->csock != -1) { - int y; - 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, (ds_get_width(vs->ds) / 16), VNC_DIRTY_WORDS); - - /* Walk through the dirty map and eliminate tiles that - really aren't dirty */ - row = ds_get_data(vs->ds); - old_row = vs->old_data; - - for (y = 0; y < ds_get_height(vs->ds); y++) { - if (vnc_and_bits(vs->dirty_row[y], width_mask, VNC_DIRTY_WORDS)) { - int x; - uint8_t *ptr; - char *old_ptr; - - ptr = row; - old_ptr = (char*)old_row; - - for (x = 0; x < ds_get_width(vs->ds); x += 16) { - if (memcmp(old_ptr, ptr, 16 * ds_get_bytes_per_pixel(vs->ds)) == 0) { - vnc_clear_bit(vs->dirty_row[y], (x / 16)); - } else { - has_dirty = 1; - memcpy(old_ptr, ptr, 16 * ds_get_bytes_per_pixel(vs->ds)); - } - - ptr += 16 * ds_get_bytes_per_pixel(vs->ds); - old_ptr += 16 * ds_get_bytes_per_pixel(vs->ds); - } - } - - row += ds_get_linesize(vs->ds); - old_row += ds_get_linesize(vs->ds); - } - - if (!has_dirty && !vs->audio_cap) { - qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock) + VNC_REFRESH_INTERVAL); - return; - } - - /* Count rectangles */ - n_rectangles = 0; - vnc_write_u8(vs, 0); /* msg id */ - vnc_write_u8(vs, 0); - saved_offset = vs->output.offset; - vnc_write_u16(vs, 0); - - for (y = 0; y < vs->serverds.height; y++) { - int x; - int last_x = -1; - for (x = 0; x < vs->serverds.width / 16; x++) { - if (vnc_get_bit(vs->dirty_row[y], x)) { - if (last_x == -1) { - last_x = x; - } - vnc_clear_bit(vs->dirty_row[y], x); - } else { - if (last_x != -1) { - int h = find_dirty_height(vs, y, last_x, x); - send_framebuffer_update(vs, last_x * 16, y, (x - last_x) * 16, h); - n_rectangles++; - } - last_x = -1; - } - } - if (last_x != -1) { - int h = find_dirty_height(vs, y, last_x, x); - send_framebuffer_update(vs, last_x * 16, y, (x - last_x) * 16, h); - n_rectangles++; - } - } - vs->output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF; - vs->output.buffer[saved_offset + 1] = n_rectangles & 0xFF; - vnc_flush(vs); - - } - - if (vs->csock != -1) { - qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock) + VNC_REFRESH_INTERVAL); + VncDisplay *vd = vs->vd; + int y; + int n_rectangles; + int saved_offset; + + if (vs->output.offset && !vs->audio_cap && !vs->force_update) + /* kernel send buffers are full -> drop frames to throttle */ + return 0; + + if (!has_dirty && !vs->audio_cap && !vs->force_update) + return 0; + + /* + * Send screen updates to the vnc client using the server + * surface and server dirty map. guest surface updates + * happening in parallel don't disturb us, the next pass will + * send them to the client. + */ + n_rectangles = 0; + vnc_write_u8(vs, 0); /* msg id */ + vnc_write_u8(vs, 0); + saved_offset = vs->output.offset; + vnc_write_u16(vs, 0); + + for (y = 0; y < vd->server->height; y++) { + int x; + int last_x = -1; + for (x = 0; x < vd->server->width / 16; x++) { + if (vnc_get_bit(vs->dirty[y], x)) { + if (last_x == -1) { + last_x = x; + } + vnc_clear_bit(vs->dirty[y], x); + } else { + if (last_x != -1) { + int h = find_and_clear_dirty_height(vs, y, last_x, x); + send_framebuffer_update(vs, last_x * 16, y, (x - last_x) * 16, h); + n_rectangles++; + } + last_x = -1; + } + } + if (last_x != -1) { + int h = find_and_clear_dirty_height(vs, y, last_x, x); + send_framebuffer_update(vs, last_x * 16, y, (x - last_x) * 16, h); + n_rectangles++; + } + } + vs->output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF; + vs->output.buffer[saved_offset + 1] = n_rectangles & 0xFF; + vnc_flush(vs); + vs->force_update = 0; + return n_rectangles; } + if (vs->csock == -1) + vnc_disconnect_finish(vs); + + return 0; } /* audio */ @@ -816,7 +888,7 @@ static void audio_add(VncState *vs) ops.destroy = audio_capture_destroy; ops.capture = audio_capture; - vs->audio_cap = AUD_add_capture(NULL, &vs->as, &ops, vs); + vs->audio_cap = AUD_add_capture(&vs->as, &ops, vs); if (!vs->audio_cap) { monitor_printf(mon, "Failed to add audio capture\n"); } @@ -830,6 +902,50 @@ static void audio_del(VncState *vs) } } +static void vnc_disconnect_start(VncState *vs) +{ + if (vs->csock == -1) + return; + qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL); + closesocket(vs->csock); + vs->csock = -1; +} + +static void vnc_disconnect_finish(VncState *vs) +{ + if (vs->input.buffer) { + qemu_free(vs->input.buffer); + vs->input.buffer = NULL; + } + if (vs->output.buffer) { + qemu_free(vs->output.buffer); + vs->output.buffer = NULL; + } +#ifdef CONFIG_VNC_TLS + vnc_tls_client_cleanup(vs); +#endif /* CONFIG_VNC_TLS */ +#ifdef CONFIG_VNC_SASL + vnc_sasl_client_cleanup(vs); +#endif /* CONFIG_VNC_SASL */ + audio_del(vs); + + VncState *p, *parent = NULL; + for (p = vs->vd->clients; p != NULL; p = p->next) { + if (p == vs) { + if (parent) + parent->next = p->next; + else + vs->vd->clients = p->next; + break; + } + parent = p; + } + if (!vs->vd->clients) + dcl->idle = 1; + + vnc_remove_timer(vs->vd); + qemu_free(vs); +} int vnc_client_io_error(VncState *vs, int ret, int last_errno) { @@ -847,39 +963,11 @@ int vnc_client_io_error(VncState *vs, int ret, int last_errno) } } - 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); - qemu_del_timer(vs->timer); - qemu_free_timer(vs->timer); - if (vs->input.buffer) qemu_free(vs->input.buffer); - if (vs->output.buffer) qemu_free(vs->output.buffer); -#ifdef CONFIG_VNC_TLS - vnc_tls_client_cleanup(vs); -#endif /* CONFIG_VNC_TLS */ -#ifdef CONFIG_VNC_SASL - vnc_sasl_client_cleanup(vs); -#endif /* CONFIG_VNC_SASL */ - audio_del(vs); - - VncState *p, *parent = NULL; - for (p = vs->vd->clients; p != NULL; p = p->next) { - if (p == vs) { - if (parent) - parent->next = p->next; - else - vs->vd->clients = p->next; - break; - } - parent = p; - } - if (!vs->vd->clients) - dcl->idle = 1; + VNC_DEBUG("Closing down client sock: ret %d, errno %d\n", + ret, ret < 0 ? last_errno : 0); + vnc_disconnect_start(vs); - qemu_free(vs->old_data); - qemu_free(vs); - - return 0; + return 0; } return ret; } @@ -887,7 +975,8 @@ int vnc_client_io_error(VncState *vs, int ret, int last_errno) void vnc_client_error(VncState *vs) { - vnc_client_io_error(vs, -1, EINVAL); + VNC_DEBUG("Closing down client sock: protocol error\n"); + vnc_disconnect_start(vs); } @@ -911,18 +1000,18 @@ long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) long ret; #ifdef CONFIG_VNC_TLS if (vs->tls.session) { - ret = gnutls_write(vs->tls.session, data, datalen); - if (ret < 0) { - if (ret == GNUTLS_E_AGAIN) - errno = EAGAIN; - else - errno = EIO; - ret = -1; - } + ret = gnutls_write(vs->tls.session, data, datalen); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EIO; + ret = -1; + } } else #endif /* CONFIG_VNC_TLS */ - ret = send(vs->csock, data, datalen, 0); - VNC_DEBUG("Wrote wire %p %d -> %ld\n", data, datalen, ret); + ret = send(vs->csock, (const void *)data, datalen, 0); + VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret); return vnc_client_io_error(vs, ret, socket_error()); } @@ -942,7 +1031,7 @@ static long vnc_client_write_plain(VncState *vs) long ret; #ifdef CONFIG_VNC_SASL - VNC_DEBUG("Write Plain: Pending output %p size %d offset %d. Wait SSF %d\n", + VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n", vs->output.buffer, vs->output.capacity, vs->output.offset, vs->sasl.waitWriteSSF); @@ -962,7 +1051,7 @@ static long vnc_client_write_plain(VncState *vs) vs->output.offset -= ret; if (vs->output.offset == 0) { - qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); } return ret; @@ -1016,18 +1105,18 @@ long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) long ret; #ifdef CONFIG_VNC_TLS if (vs->tls.session) { - ret = gnutls_read(vs->tls.session, data, datalen); - if (ret < 0) { - if (ret == GNUTLS_E_AGAIN) - errno = EAGAIN; - else - errno = EIO; - ret = -1; - } + ret = gnutls_read(vs->tls.session, data, datalen); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EIO; + ret = -1; + } } else #endif /* CONFIG_VNC_TLS */ - ret = recv(vs->csock, data, datalen, 0); - VNC_DEBUG("Read wire %p %d -> %ld\n", data, datalen, ret); + ret = recv(vs->csock, (void *)data, datalen, 0); + VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret); return vnc_client_io_error(vs, ret, socket_error()); } @@ -1043,7 +1132,7 @@ long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) static long vnc_client_read_plain(VncState *vs) { int ret; - VNC_DEBUG("Read plain %p size %d offset %d\n", + VNC_DEBUG("Read plain %p size %zd offset %zd\n", vs->input.buffer, vs->input.capacity, vs->input.offset); buffer_reserve(&vs->input, 4096); ret = vnc_client_read_buf(vs, buffer_end(&vs->input), 4096); @@ -1070,23 +1159,28 @@ void vnc_client_read(void *opaque) else #endif /* CONFIG_VNC_SASL */ ret = vnc_client_read_plain(vs); - if (!ret) - return; + if (!ret) { + if (vs->csock == -1) + vnc_disconnect_finish(vs); + return; + } while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) { - size_t len = vs->read_handler_expect; - int ret; + size_t len = vs->read_handler_expect; + int ret; - ret = vs->read_handler(vs, vs->input.buffer, len); - if (vs->csock == -1) - return; + ret = vs->read_handler(vs, vs->input.buffer, len); + if (vs->csock == -1) { + vnc_disconnect_finish(vs); + return; + } - if (!ret) { - memmove(vs->input.buffer, vs->input.buffer + len, (vs->input.offset - len)); - vs->input.offset -= len; - } else { - vs->read_handler_expect = ret; - } + if (!ret) { + memmove(vs->input.buffer, vs->input.buffer + len, (vs->input.offset - len)); + vs->input.offset -= len; + } else { + vs->read_handler_expect = ret; + } } } @@ -1094,8 +1188,8 @@ void vnc_write(VncState *vs, const void *data, size_t len) { buffer_reserve(&vs->output, len); - if (buffer_empty(&vs->output)) { - qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs); + if (vs->csock != -1 && buffer_empty(&vs->output)) { + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs); } buffer_append(&vs->output, data, len); @@ -1135,8 +1229,8 @@ void vnc_write_u8(VncState *vs, uint8_t value) void vnc_flush(VncState *vs) { - if (vs->output.offset) - vnc_client_write(vs); + if (vs->csock != -1 && vs->output.offset) + vnc_client_write(vs); } uint8_t read_u8(uint8_t *data, size_t offset) @@ -1152,13 +1246,13 @@ uint16_t read_u16(uint8_t *data, size_t offset) int32_t read_s32(uint8_t *data, size_t offset) { return (int32_t)((data[offset] << 24) | (data[offset + 1] << 16) | - (data[offset + 2] << 8) | data[offset + 3]); + (data[offset + 2] << 8) | data[offset + 3]); } uint32_t read_u32(uint8_t *data, size_t offset) { return ((data[offset] << 24) | (data[offset + 1] << 16) | - (data[offset + 2] << 8) | data[offset + 3]); + (data[offset + 2] << 8) | data[offset + 3]); } static void client_cut_text(VncState *vs, size_t len, uint8_t *text) @@ -1168,13 +1262,13 @@ static void client_cut_text(VncState *vs, size_t len, uint8_t *text) static void check_pointer_type_change(VncState *vs, int absolute) { if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE) && vs->absolute != absolute) { - vnc_write_u8(vs, 0); - vnc_write_u8(vs, 0); - vnc_write_u16(vs, 1); - vnc_framebuffer_update(vs, absolute, 0, - ds_get_width(vs->ds), ds_get_height(vs->ds), + vnc_write_u8(vs, 0); + vnc_write_u8(vs, 0); + vnc_write_u16(vs, 1); + vnc_framebuffer_update(vs, absolute, 0, + ds_get_width(vs->ds), ds_get_height(vs->ds), VNC_ENCODING_POINTER_TYPE_CHANGE); - vnc_flush(vs); + vnc_flush(vs); } vs->absolute = absolute; } @@ -1185,32 +1279,32 @@ static void pointer_event(VncState *vs, int button_mask, int x, int y) int dz = 0; if (button_mask & 0x01) - buttons |= MOUSE_EVENT_LBUTTON; + buttons |= MOUSE_EVENT_LBUTTON; if (button_mask & 0x02) - buttons |= MOUSE_EVENT_MBUTTON; + buttons |= MOUSE_EVENT_MBUTTON; if (button_mask & 0x04) - buttons |= MOUSE_EVENT_RBUTTON; + buttons |= MOUSE_EVENT_RBUTTON; if (button_mask & 0x08) - dz = -1; + dz = -1; if (button_mask & 0x10) - dz = 1; + dz = 1; if (vs->absolute) { - kbd_mouse_event(x * 0x7FFF / (ds_get_width(vs->ds) - 1), - y * 0x7FFF / (ds_get_height(vs->ds) - 1), - dz, buttons); + kbd_mouse_event(x * 0x7FFF / (ds_get_width(vs->ds) - 1), + y * 0x7FFF / (ds_get_height(vs->ds) - 1), + dz, buttons); } else if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE)) { - x -= 0x7FFF; - y -= 0x7FFF; + x -= 0x7FFF; + y -= 0x7FFF; - kbd_mouse_event(x, y, dz, buttons); + kbd_mouse_event(x, y, dz, buttons); } else { - if (vs->last_x != -1) - kbd_mouse_event(x - vs->last_x, - y - vs->last_y, - dz, buttons); - vs->last_x = x; - vs->last_y = y; + if (vs->last_x != -1) + kbd_mouse_event(x - vs->last_x, + y - vs->last_y, + dz, buttons); + vs->last_x = x; + vs->last_y = y; } check_pointer_type_change(vs, kbd_mouse_is_absolute()); @@ -1258,8 +1352,8 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym) return; } break; - case 0x3a: /* CapsLock */ - case 0x45: /* NumLock */ + case 0x3a: /* CapsLock */ + case 0x45: /* NumLock */ if (!down) vs->modifiers_state[keycode] ^= 1; break; @@ -1283,6 +1377,27 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym) } } + if ((sym >= 'A' && sym <= 'Z') || (sym >= 'a' && sym <= 'z')) { + /* If the capslock state needs to change then simulate an additional + keypress before sending this one. This will happen if the user + 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]); + if (capslock) { + if (uppercase == shift) { + vs->modifiers_state[0x3a] = 0; + press_key(vs, 0xffe5); + } + } else { + if (uppercase != shift) { + vs->modifiers_state[0x3a] = 1; + press_key(vs, 0xffe5); + } + } + } + if (is_graphic_console()) { if (keycode & 0x80) kbd_put_keycode(0xe0); @@ -1293,6 +1408,7 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym) } else { /* QEMU console emulation */ if (down) { + int numlock = vs->modifiers_state[0x45]; switch (keycode) { case 0x2a: /* Left Shift */ case 0x36: /* Right Shift */ @@ -1328,6 +1444,57 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym) case 0xd1: kbd_put_keysym(QEMU_KEY_PAGEDOWN); break; + + case 0x47: + kbd_put_keysym(numlock ? '7' : QEMU_KEY_HOME); + break; + case 0x48: + kbd_put_keysym(numlock ? '8' : QEMU_KEY_UP); + break; + case 0x49: + kbd_put_keysym(numlock ? '9' : QEMU_KEY_PAGEUP); + break; + case 0x4b: + kbd_put_keysym(numlock ? '4' : QEMU_KEY_LEFT); + break; + case 0x4c: + kbd_put_keysym('5'); + break; + case 0x4d: + kbd_put_keysym(numlock ? '6' : QEMU_KEY_RIGHT); + break; + case 0x4f: + kbd_put_keysym(numlock ? '1' : QEMU_KEY_END); + break; + case 0x50: + kbd_put_keysym(numlock ? '2' : QEMU_KEY_DOWN); + break; + case 0x51: + kbd_put_keysym(numlock ? '3' : QEMU_KEY_PAGEDOWN); + break; + case 0x52: + kbd_put_keysym('0'); + break; + case 0x53: + kbd_put_keysym(numlock ? '.' : QEMU_KEY_DELETE); + break; + + case 0xb5: + kbd_put_keysym('/'); + break; + case 0x37: + kbd_put_keysym('*'); + break; + case 0x4a: + kbd_put_keysym('-'); + break; + case 0x4e: + kbd_put_keysym('+'); + break; + case 0x9c: + kbd_put_keysym('\n'); + break; + default: kbd_put_keysym(sym); break; @@ -1341,7 +1508,7 @@ static void key_event(VncState *vs, int down, uint32_t sym) int keycode; if (sym >= 'A' && sym <= 'Z' && is_graphic_console()) - sym = sym - 'A' + 'a'; + sym = sym - 'A' + 'a'; keycode = keysym2scancode(vs->vd->kbd_layout, sym & 0xFFFF); do_key_event(vs, down, keycode, sym); @@ -1358,8 +1525,8 @@ static void ext_key_event(VncState *vs, int down, } static void framebuffer_update_request(VncState *vs, int incremental, - int x_position, int y_position, - int w, int h) + int x_position, int y_position, + int w, int h) { if (x_position > ds_get_width(vs->ds)) x_position = ds_get_width(vs->ds); @@ -1373,14 +1540,11 @@ static void framebuffer_update_request(VncState *vs, int incremental, int i; vs->need_update = 1; if (!incremental) { - 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->force_update = 1; + for (i = 0; i < h; i++) { + vnc_set_bits(vs->dirty[y_position + i], (ds_get_width(vs->ds) / 16), VNC_DIRTY_WORDS); - memset(old_row, 42, ds_get_width(vs->ds) * ds_get_bytes_per_pixel(vs->ds)); - old_row += ds_get_linesize(vs->ds); - } + } } } @@ -1497,17 +1661,17 @@ static void set_pixel_conversion(VncState *vs) } static void set_pixel_format(VncState *vs, - int bits_per_pixel, int depth, - int big_endian_flag, int true_color_flag, - int red_max, int green_max, int blue_max, - int red_shift, int green_shift, int blue_shift) + int bits_per_pixel, int depth, + int big_endian_flag, int true_color_flag, + int red_max, int green_max, int blue_max, + int red_shift, int green_shift, int blue_shift) { if (!true_color_flag) { - vnc_client_error(vs); + vnc_client_error(vs); return; } - vs->clientds = vs->serverds; + vs->clientds = *(vs->vd->guest.ds); vs->clientds.pf.rmax = red_max; count_bits(vs->clientds.pf.rbits, red_max); vs->clientds.pf.rshift = red_shift; @@ -1537,7 +1701,7 @@ static void pixel_format_message (VncState *vs) { vnc_write_u8(vs, vs->ds->surface->pf.bits_per_pixel); /* bits-per-pixel */ vnc_write_u8(vs, vs->ds->surface->pf.depth); /* depth */ -#ifdef WORDS_BIGENDIAN +#ifdef HOST_WORDS_BIGENDIAN vnc_write_u8(vs, 1); /* big-endian-flag */ #else vnc_write_u8(vs, 0); /* big-endian-flag */ @@ -1556,7 +1720,7 @@ static void pixel_format_message (VncState *vs) { else if (vs->ds->surface->pf.bits_per_pixel == 8) vs->send_hextile_tile = send_hextile_tile_8; vs->clientds = *(vs->ds->surface); - vs->clientds.flags |= ~QEMU_ALLOCATED_FLAG; + vs->clientds.flags &= ~QEMU_ALLOCATED_FLAG; vs->write_pixels = vnc_write_pixels_copy; vnc_write(vs, pad, 3); /* padding */ @@ -1587,68 +1751,75 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) { int i; uint16_t limit; + VncDisplay *vd = vs->vd; + + if (data[0] > 3) { + vd->timer_interval = VNC_REFRESH_INTERVAL_BASE; + if (!qemu_timer_expired(vd->timer, qemu_get_clock(rt_clock) + vd->timer_interval)) + qemu_mod_timer(vd->timer, qemu_get_clock(rt_clock) + vd->timer_interval); + } switch (data[0]) { case 0: - if (len == 1) - return 20; - - set_pixel_format(vs, read_u8(data, 4), read_u8(data, 5), - read_u8(data, 6), read_u8(data, 7), - read_u16(data, 8), read_u16(data, 10), - read_u16(data, 12), read_u8(data, 14), - read_u8(data, 15), read_u8(data, 16)); - break; + if (len == 1) + return 20; + + set_pixel_format(vs, read_u8(data, 4), read_u8(data, 5), + read_u8(data, 6), read_u8(data, 7), + read_u16(data, 8), read_u16(data, 10), + read_u16(data, 12), read_u8(data, 14), + read_u8(data, 15), read_u8(data, 16)); + break; case 2: - if (len == 1) - return 4; + if (len == 1) + return 4; - if (len == 4) { + if (len == 4) { limit = read_u16(data, 2); if (limit > 0) return 4 + (limit * 4); } else 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)); - } + for (i = 0; i < limit; i++) { + int32_t val = read_s32(data, 4 + (i * 4)); + memcpy(data + 4 + (i * 4), &val, sizeof(val)); + } - set_encodings(vs, (int32_t *)(data + 4), limit); - break; + set_encodings(vs, (int32_t *)(data + 4), limit); + break; case 3: - if (len == 1) - return 10; + if (len == 1) + return 10; - framebuffer_update_request(vs, - read_u8(data, 1), read_u16(data, 2), read_u16(data, 4), - read_u16(data, 6), read_u16(data, 8)); - break; + framebuffer_update_request(vs, + read_u8(data, 1), read_u16(data, 2), read_u16(data, 4), + read_u16(data, 6), read_u16(data, 8)); + break; case 4: - if (len == 1) - return 8; + if (len == 1) + return 8; - key_event(vs, read_u8(data, 1), read_u32(data, 4)); - break; + key_event(vs, read_u8(data, 1), read_u32(data, 4)); + break; case 5: - if (len == 1) - return 6; + if (len == 1) + return 6; - pointer_event(vs, read_u8(data, 1), read_u16(data, 2), read_u16(data, 4)); - break; + pointer_event(vs, read_u8(data, 1), read_u16(data, 2), read_u16(data, 4)); + break; case 6: - if (len == 1) - return 8; + if (len == 1) + return 8; - if (len == 8) { + 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; + client_cut_text(vs, read_u32(data, 4), data + 8); + break; case 255: if (len == 1) return 2; @@ -1710,9 +1881,9 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) } break; default: - printf("Msg: %d\n", data[0]); - vnc_client_error(vs); - break; + printf("Msg: %d\n", data[0]); + vnc_client_error(vs); + break; } vnc_read_when(vs, protocol_client_msg, 1); @@ -1765,16 +1936,16 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len) unsigned char key[8]; if (!vs->vd->password || !vs->vd->password[0]) { - VNC_DEBUG("No password configured on server"); - vnc_write_u32(vs, 1); /* Reject auth */ - if (vs->minor >= 8) { - static const char err[] = "Authentication failed"; - vnc_write_u32(vs, sizeof(err)); - vnc_write(vs, err, sizeof(err)); - } - vnc_flush(vs); - vnc_client_error(vs); - return 0; + VNC_DEBUG("No password configured on server"); + vnc_write_u32(vs, 1); /* Reject auth */ + if (vs->minor >= 8) { + static const char err[] = "Authentication failed"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_flush(vs); + vnc_client_error(vs); + return 0; } memcpy(response, vs->challenge, VNC_AUTH_CHALLENGE_SIZE); @@ -1789,19 +1960,19 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len) /* Compare expected vs actual challenge response */ if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) { - VNC_DEBUG("Client challenge reponse did not match\n"); - vnc_write_u32(vs, 1); /* Reject auth */ - if (vs->minor >= 8) { - static const char err[] = "Authentication failed"; - vnc_write_u32(vs, sizeof(err)); - vnc_write(vs, err, sizeof(err)); - } - vnc_flush(vs); - vnc_client_error(vs); + VNC_DEBUG("Client challenge reponse did not match\n"); + vnc_write_u32(vs, 1); /* Reject auth */ + if (vs->minor >= 8) { + static const char err[] = "Authentication failed"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_flush(vs); + vnc_client_error(vs); } else { - VNC_DEBUG("Accepting VNC challenge response\n"); - vnc_write_u32(vs, 0); /* Accept auth */ - vnc_flush(vs); + VNC_DEBUG("Accepting VNC challenge response\n"); + vnc_write_u32(vs, 0); /* Accept auth */ + vnc_flush(vs); start_client_init(vs); } @@ -1824,7 +1995,7 @@ 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 */ if (data[0] != vs->vd->auth) { /* Reject auth */ - VNC_DEBUG("Reject auth %d\n", (int)data[0]); + VNC_DEBUG("Reject auth %d because it didn't match advertized\n", (int)data[0]); vnc_write_u32(vs, 1); if (vs->minor >= 8) { static const char err[] = "Authentication failed"; @@ -1864,7 +2035,7 @@ static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len) #endif /* CONFIG_VNC_SASL */ default: /* Should not be possible, but just in case */ - VNC_DEBUG("Reject auth %d\n", vs->vd->auth); + VNC_DEBUG("Reject auth %d server code bug\n", vs->vd->auth); vnc_write_u8(vs, 1); if (vs->minor >= 8) { static const char err[] = "Authentication failed"; @@ -1885,35 +2056,35 @@ static int protocol_version(VncState *vs, uint8_t *version, size_t len) local[12] = 0; if (sscanf(local, "RFB %03d.%03d\n", &vs->major, &vs->minor) != 2) { - VNC_DEBUG("Malformed protocol version %s\n", local); - vnc_client_error(vs); - return 0; + VNC_DEBUG("Malformed protocol version %s\n", local); + vnc_client_error(vs); + return 0; } 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)) { - VNC_DEBUG("Unsupported client version\n"); - vnc_write_u32(vs, VNC_AUTH_INVALID); - vnc_flush(vs); - vnc_client_error(vs); - return 0; + (vs->minor != 3 && + vs->minor != 4 && + vs->minor != 5 && + vs->minor != 7 && + vs->minor != 8)) { + VNC_DEBUG("Unsupported client version\n"); + vnc_write_u32(vs, VNC_AUTH_INVALID); + vnc_flush(vs); + vnc_client_error(vs); + return 0; } /* 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 == 4 || vs->minor == 5) - vs->minor = 3; + vs->minor = 3; if (vs->minor == 3) { - if (vs->vd->auth == VNC_AUTH_NONE) { + if (vs->vd->auth == VNC_AUTH_NONE) { VNC_DEBUG("Tell client auth none\n"); vnc_write_u32(vs, vs->vd->auth); vnc_flush(vs); - start_client_init(vs); + start_client_init(vs); } else if (vs->vd->auth == VNC_AUTH_VNC) { VNC_DEBUG("Tell client VNC auth\n"); vnc_write_u32(vs, vs->vd->auth); @@ -1926,16 +2097,112 @@ static int protocol_version(VncState *vs, uint8_t *version, size_t len) vnc_client_error(vs); } } else { - VNC_DEBUG("Telling client we support auth %d\n", vs->vd->auth); - vnc_write_u8(vs, 1); /* num auth */ - vnc_write_u8(vs, vs->vd->auth); - vnc_read_when(vs, protocol_client_auth, 1); - vnc_flush(vs); + VNC_DEBUG("Telling client we support auth %d\n", vs->vd->auth); + vnc_write_u8(vs, 1); /* num auth */ + vnc_write_u8(vs, vs->vd->auth); + vnc_read_when(vs, protocol_client_auth, 1); + vnc_flush(vs); } return 0; } +static int vnc_refresh_server_surface(VncDisplay *vd) +{ + int y; + uint8_t *guest_row; + uint8_t *server_row; + int cmp_bytes; + uint32_t width_mask[VNC_DIRTY_WORDS]; + VncState *vs = NULL; + int has_dirty = 0; + + /* + * Walk through the guest dirty map. + * Check and copy modified bits from guest to server surface. + * Update server dirty map. + */ + vnc_set_bits(width_mask, (ds_get_width(vd->ds) / 16), VNC_DIRTY_WORDS); + cmp_bytes = 16 * ds_get_bytes_per_pixel(vd->ds); + guest_row = vd->guest.ds->data; + server_row = vd->server->data; + for (y = 0; y < vd->guest.ds->height; y++) { + if (vnc_and_bits(vd->guest.dirty[y], width_mask, VNC_DIRTY_WORDS)) { + int x; + uint8_t *guest_ptr; + uint8_t *server_ptr; + + guest_ptr = guest_row; + server_ptr = server_row; + + for (x = 0; x < vd->guest.ds->width; + x += 16, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) { + if (!vnc_get_bit(vd->guest.dirty[y], (x / 16))) + continue; + vnc_clear_bit(vd->guest.dirty[y], (x / 16)); + if (memcmp(server_ptr, guest_ptr, cmp_bytes) == 0) + continue; + memcpy(server_ptr, guest_ptr, cmp_bytes); + vs = vd->clients; + while (vs != NULL) { + vnc_set_bit(vs->dirty[y], (x / 16)); + vs = vs->next; + } + has_dirty++; + } + } + guest_row += ds_get_linesize(vd->ds); + server_row += ds_get_linesize(vd->ds); + } + return has_dirty; +} + +static void vnc_refresh(void *opaque) +{ + VncDisplay *vd = opaque; + VncState *vs = NULL; + int has_dirty = 0, rects = 0; + + vga_hw_update(); + + has_dirty = vnc_refresh_server_surface(vd); + + vs = vd->clients; + while (vs != NULL) { + rects += vnc_update_client(vs, has_dirty); + vs = vs->next; + } + + if (has_dirty && rects) { + vd->timer_interval /= 2; + if (vd->timer_interval < VNC_REFRESH_INTERVAL_BASE) + vd->timer_interval = VNC_REFRESH_INTERVAL_BASE; + } else { + vd->timer_interval += VNC_REFRESH_INTERVAL_INC; + if (vd->timer_interval > VNC_REFRESH_INTERVAL_MAX) + vd->timer_interval = VNC_REFRESH_INTERVAL_MAX; + } + qemu_mod_timer(vd->timer, qemu_get_clock(rt_clock) + vd->timer_interval); +} + +static void vnc_init_timer(VncDisplay *vd) +{ + vd->timer_interval = VNC_REFRESH_INTERVAL_BASE; + if (vd->timer == NULL && vd->clients != NULL) { + vd->timer = qemu_new_timer(rt_clock, vnc_refresh, vd); + vnc_refresh(vd); + } +} + +static void vnc_remove_timer(VncDisplay *vd) +{ + if (vd->timer != NULL && vd->clients == NULL) { + qemu_del_timer(vd->timer); + qemu_free_timer(vd->timer); + vd->timer = NULL; + } +} + static void vnc_connect(VncDisplay *vd, int csock) { VncState *vs = qemu_mallocz(sizeof(VncState)); @@ -1948,7 +2215,6 @@ static void vnc_connect(VncDisplay *vd, int csock) vs->vd = vd; vs->ds = vd->ds; - vs->timer = qemu_new_timer(rt_clock, vnc_update_client, vs); vs->last_x = -1; vs->last_y = -1; @@ -1957,17 +2223,19 @@ static void vnc_connect(VncDisplay *vd, int csock) vs->as.fmt = AUD_FMT_S16; vs->as.endianness = 0; - vnc_resize(vs); + vs->next = vd->clients; + vd->clients = vs; + + vga_hw_update(); + 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)); - vnc_update_client(vs); reset_keys(vs); - vs->next = vd->clients; - vd->clients = vs; + vnc_init_timer(vd); + + /* vs might be free()ed here */ } static void vnc_listen_read(void *opaque) @@ -1979,7 +2247,7 @@ static void vnc_listen_read(void *opaque) /* Catch-up */ vga_hw_update(); - int csock = accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); + int csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); if (csock != -1) { vnc_connect(vs, csock); } @@ -1987,9 +2255,8 @@ static void vnc_listen_read(void *opaque) void vnc_display_init(DisplayState *ds) { - VncDisplay *vs; + VncDisplay *vs = qemu_mallocz(sizeof(*vs)); - vs = qemu_mallocz(sizeof(VncState)); dcl = qemu_mallocz(sizeof(DisplayChangeListener)); ds->opaque = vs; @@ -2006,7 +2273,7 @@ void vnc_display_init(DisplayState *ds) vs->kbd_layout = init_keyboard_layout(name2keysym, "en-us"); if (!vs->kbd_layout) - exit(1); + exit(1); dcl->dpy_copy = vnc_dpy_copy; dcl->dpy_update = vnc_dpy_update; @@ -2023,13 +2290,13 @@ void vnc_display_close(DisplayState *ds) if (!vs) return; if (vs->display) { - qemu_free(vs->display); - vs->display = NULL; + qemu_free(vs->display); + vs->display = NULL; } if (vs->lsock != -1) { - qemu_set_fd_handler2(vs->lsock, NULL, NULL, NULL, NULL); - close(vs->lsock); - vs->lsock = -1; + qemu_set_fd_handler2(vs->lsock, NULL, NULL, NULL, NULL); + close(vs->lsock); + vs->lsock = -1; } vs->auth = VNC_AUTH_INVALID; #ifdef CONFIG_VNC_TLS @@ -2042,18 +2309,34 @@ int vnc_display_password(DisplayState *ds, const char *password) { VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; + if (!vs) { + return -1; + } + if (vs->password) { - qemu_free(vs->password); - vs->password = NULL; + qemu_free(vs->password); + vs->password = NULL; } if (password && password[0]) { - if (!(vs->password = qemu_strdup(password))) - return -1; + if (!(vs->password = qemu_strdup(password))) + return -1; + if (vs->auth == VNC_AUTH_NONE) { + vs->auth = VNC_AUTH_VNC; + } + } else { + vs->auth = VNC_AUTH_NONE; } return 0; } +char *vnc_display_local_addr(DisplayState *ds) +{ + VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; + + return vnc_socket_local_addr("%s:%s", vs->lsock); +} + int vnc_display_open(DisplayState *ds, const char *display) { VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; @@ -2068,64 +2351,84 @@ int vnc_display_open(DisplayState *ds, const char *display) int sasl = 0; int saslErr; #endif + int acl = 0; if (!vnc_display) return -1; vnc_display_close(ds); if (strcmp(display, "none") == 0) - return 0; + return 0; if (!(vs->display = strdup(display))) - return -1; + return -1; options = display; while ((options = strchr(options, ','))) { - options++; - 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) { + options++; + 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_SASL - } else if (strncmp(options, "sasl", 4) == 0) { - sasl = 1; /* Require SASL auth */ + } else if (strncmp(options, "sasl", 4) == 0) { + sasl = 1; /* Require SASL auth */ +#endif +#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->tls.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_tls_set_x509_creds_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 + } else if (strncmp(options, "acl", 3) == 0) { + acl = 1; + } + } + #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->tls.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_tls_set_x509_creds_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; - } + if (acl && x509 && vs->tls.x509verify) { + if (!(vs->tls.acl = qemu_acl_init("vnc.x509dname"))) { + fprintf(stderr, "Failed to create x509 dname ACL\n"); + exit(1); + } + } #endif - } +#ifdef CONFIG_VNC_SASL + if (acl && sasl) { + if (!(vs->sasl.acl = qemu_acl_init("vnc.username"))) { + fprintf(stderr, "Failed to create username ACL\n"); + exit(1); + } } +#endif /* * Combinations we support here: @@ -2145,22 +2448,22 @@ int vnc_display_open(DisplayState *ds, const char *display) */ if (password) { #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 { + 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 /* CONFIG_VNC_TLS */ - VNC_DEBUG("Initializing VNC server with password auth\n"); - vs->auth = VNC_AUTH_VNC; + VNC_DEBUG("Initializing VNC server with password auth\n"); + vs->auth = VNC_AUTH_VNC; #ifdef CONFIG_VNC_TLS - vs->subauth = VNC_AUTH_INVALID; - } + vs->subauth = VNC_AUTH_INVALID; + } #endif /* CONFIG_VNC_TLS */ #ifdef CONFIG_VNC_SASL } else if (sasl) { @@ -2168,15 +2471,15 @@ int vnc_display_open(DisplayState *ds, const char *display) if (tls) { vs->auth = VNC_AUTH_VENCRYPT; if (x509) { - VNC_DEBUG("Initializing VNC server with x509 SASL auth\n"); + VNC_DEBUG("Initializing VNC server with x509 SASL auth\n"); vs->subauth = VNC_AUTH_VENCRYPT_X509SASL; } else { - VNC_DEBUG("Initializing VNC server with TLS SASL auth\n"); + VNC_DEBUG("Initializing VNC server with TLS SASL auth\n"); vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL; } } else { #endif /* CONFIG_VNC_TLS */ - VNC_DEBUG("Initializing VNC server with SASL auth\n"); + VNC_DEBUG("Initializing VNC server with SASL auth\n"); vs->auth = VNC_AUTH_SASL; #ifdef CONFIG_VNC_TLS vs->subauth = VNC_AUTH_INVALID; @@ -2185,22 +2488,22 @@ int vnc_display_open(DisplayState *ds, const char *display) #endif /* CONFIG_VNC_SASL */ } 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 { + 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; + VNC_DEBUG("Initializing VNC server with no auth\n"); + vs->auth = VNC_AUTH_NONE; #ifdef CONFIG_VNC_TLS - vs->subauth = VNC_AUTH_INVALID; - } + vs->subauth = VNC_AUTH_INVALID; + } #endif }