diff --git a/qemu-sasl-01-tls-handshake-fix.patch b/qemu-sasl-01-tls-handshake-fix.patch new file mode 100644 index 0000000..6cc39c9 --- /dev/null +++ b/qemu-sasl-01-tls-handshake-fix.patch @@ -0,0 +1,42 @@ +This patch was previously posted here: + + http://lists.gnu.org/archive/html/qemu-devel/2009-02/msg00820.html + +In the case where the TLS handshake does *not* block on I/O, QEMU +sends the next 'start sub-auth' message twice. This seriously confuses +the VNC client :-) Fortunately the chances of the handshake not blocking +are close to zero for a TCP socket, which is why it has not been noticed +thus far. Even with both client & server on localhost, I can only hit the +bug 1 time in 20. + +NB, the diff context here is not too informative. If you look at the +full code you'll see that a few lines early we called vnc_start_tls() +which called vnc_continue_handshake() which called the method +start_auth_vencrypt_subauth(). Hence, fixing the bug, just involves +removing the 2nd bogus call to start_auth_vencrypt_subauth() as per +this patch. + + + vnc.c | 8 -------- + 1 file changed, 8 deletions(-) + + Signed-off-by: Daniel P. Berrange + +diff -r ff004fb525e7 vnc.c +--- a/vnc.c Thu Feb 19 11:26:55 2009 +0000 ++++ b/vnc.c Thu Feb 19 11:27:44 2009 +0000 +@@ -2096,14 +2096,6 @@ static int protocol_client_vencrypt_auth + 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; + } diff --git a/qemu-sasl-02-vnc-monitor-info.patch b/qemu-sasl-02-vnc-monitor-info.patch new file mode 100644 index 0000000..a5dd517 --- /dev/null +++ b/qemu-sasl-02-vnc-monitor-info.patch @@ -0,0 +1,190 @@ +The current 'info vnc' monitor output just displays the VNC server address +as provided by the -vnc command line flag. This isn't particularly useful +since it doesn't tell you what VNC is actually listening on. eg, if you +use '-vnc :1' it is useful to know whether this translated to '0.0.0.0:5901' +or chose IPv6 ':::5901'. It is also useful to know the address of the +client that is currently connected. It is also useful to know the active +authentication (if any). + +This patch tweaks the monitor output to look like: + + (qemu) info vnc + Server: + address: 0.0.0.0:5902 + auth: vencrypt+x509 + Client: none + +And when 2 clients are connected + + (qemu) info vnc + Server: + address: 0.0.0.0:5902 + auth: vencrypt+x509 + Client: + address: 10.33.6.67:38621 + Client: + address: 10.33.6.63:38620 + +More data will be added to this later in the patch series... + +The 'addr_to_string' helper method in this patch is overly generic +for the needs of this patch alone. This is because it will be re-used +by the later SASL patches in this series, where the flexibility is +important. + + + vnc.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 127 insertions(+), 10 deletions(-) + + Signed-off-by: Daniel P. Berrange + +diff -r 4dbf24b567c4 vnc.c +--- a/vnc.c Thu Feb 19 11:27:44 2009 +0000 ++++ b/vnc.c Thu Feb 19 11:53:31 2009 +0000 +@@ -166,19 +166,136 @@ struct VncState + static VncDisplay *vnc_display; /* needed for info vnc */ + static DisplayChangeListener *dcl; + ++static char *addr_to_string(const char *format, ++ struct sockaddr_storage *sa, ++ socklen_t salen) { ++ char *addr; ++ char host[NI_MAXHOST]; ++ char serv[NI_MAXSERV]; ++ int err; ++ ++ if ((err = getnameinfo((struct sockaddr *)sa, salen, ++ host, sizeof(host), ++ serv, sizeof(serv), ++ NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { ++ VNC_DEBUG("Cannot resolve address %d: %s\n", ++ err, gai_strerror(err)); ++ return NULL; ++ } ++ ++ if (asprintf(&addr, format, host, serv) < 0) ++ return NULL; ++ ++ return addr; ++} ++ ++static char *vnc_socket_local_addr(const char *format, int fd) { ++ struct sockaddr_storage sa; ++ socklen_t salen; ++ ++ salen = sizeof(sa); ++ if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) ++ return NULL; ++ ++ return addr_to_string(format, &sa, salen); ++} ++ ++static char *vnc_socket_remote_addr(const char *format, int fd) { ++ struct sockaddr_storage sa; ++ socklen_t salen; ++ ++ salen = sizeof(sa); ++ if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) ++ return NULL; ++ ++ return addr_to_string(format, &sa, salen); ++} ++ ++static const char *vnc_auth_name(VncDisplay *vd) { ++ switch (vd->auth) { ++ case VNC_AUTH_INVALID: ++ return "invalid"; ++ case VNC_AUTH_NONE: ++ return "none"; ++ case VNC_AUTH_VNC: ++ return "vnc"; ++ case VNC_AUTH_RA2: ++ return "ra2"; ++ case VNC_AUTH_RA2NE: ++ return "ra2ne"; ++ case VNC_AUTH_TIGHT: ++ return "tight"; ++ case VNC_AUTH_ULTRA: ++ return "ultra"; ++ case VNC_AUTH_TLS: ++ return "tls"; ++ case VNC_AUTH_VENCRYPT: ++#ifdef CONFIG_VNC_TLS ++ switch (vd->subauth) { ++ case VNC_AUTH_VENCRYPT_PLAIN: ++ return "vencrypt+plain"; ++ case VNC_AUTH_VENCRYPT_TLSNONE: ++ return "vencrypt+tls+none"; ++ case VNC_AUTH_VENCRYPT_TLSVNC: ++ return "vencrypt+tls+vnc"; ++ case VNC_AUTH_VENCRYPT_TLSPLAIN: ++ return "vencrypt+tls+plain"; ++ case VNC_AUTH_VENCRYPT_X509NONE: ++ return "vencrypt+x509+none"; ++ case VNC_AUTH_VENCRYPT_X509VNC: ++ return "vencrypt+x509+vnc"; ++ case VNC_AUTH_VENCRYPT_X509PLAIN: ++ return "vencrypt+x509+plain"; ++ default: ++ return "vencrypt"; ++ } ++#else ++ return "vencrypt"; ++#endif ++ } ++ return "unknown"; ++} ++ ++#define VNC_SOCKET_FORMAT_PRETTY "local %s:%s" ++ ++static void do_info_vnc_client(VncState *client) ++{ ++ char *clientAddr = ++ vnc_socket_remote_addr(" address: %s:%s\n", ++ client->csock); ++ if (!clientAddr) ++ return; ++ ++ term_puts("Client:\n"); ++ term_puts(clientAddr); ++ free(clientAddr); ++} ++ + void do_info_vnc(void) + { +- if (vnc_display == NULL || vnc_display->display == NULL) +- term_printf("VNC server disabled\n"); +- else { +- term_printf("VNC server active on: "); +- term_print_filename(vnc_display->display); +- term_printf("\n"); ++ if (vnc_display == NULL || vnc_display->display == NULL) { ++ term_printf("Server: disabled\n"); ++ } else { ++ char *serverAddr = vnc_socket_local_addr(" address: %s:%s\n", ++ vnc_display->lsock); + +- if (vnc_display->clients == NULL) +- term_printf("No client connected\n"); +- else +- term_printf("Client connected\n"); ++ if (!serverAddr) ++ return; ++ ++ term_puts("Server:\n"); ++ term_puts(serverAddr); ++ free(serverAddr); ++ term_printf(" auth: %s\n", vnc_auth_name(vnc_display)); ++ ++ if (vnc_display->clients) { ++ VncState *client = vnc_display->clients; ++ while (client) { ++ do_info_vnc_client(client); ++ client = client->next; ++ } ++ } else { ++ term_printf("Client: none\n"); ++ } + } + } + diff --git a/qemu-sasl-03-display-keymaps.patch b/qemu-sasl-03-display-keymaps.patch new file mode 100644 index 0000000..4303760 --- /dev/null +++ b/qemu-sasl-03-display-keymaps.patch @@ -0,0 +1,343 @@ +Each of the graphical frontends #include a .c file, for keymap code +resulting in duplicated definitions & duplicated compiled code. A +couple of small changes allowed this to be sanitized, so instead of +doing a #include "keymaps.c", duplicating all code, we can have a +shared keymaps.h file, and only compile code once. This allows the +next patch to move the VncState struct out into a header file without +causing clashing definitions. + + + Makefile | 9 +++++--- + b/keymaps.h | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + curses.c | 3 -- + curses_keys.h | 9 +++----- + keymaps.c | 45 ++++++++++++++++--------------------------- + sdl.c | 3 -- + sdl_keysym.h | 7 ++---- + vnc.c | 5 +--- + vnc_keysym.h | 7 ++---- + 9 files changed, 97 insertions(+), 51 deletions(-) + + Signed-off-by: Daniel P. Berrange + +diff -r 9ccd7a5b0382 Makefile +--- a/Makefile Thu Feb 19 11:54:21 2009 +0000 ++++ b/Makefile Thu Feb 19 13:15:19 2009 +0000 +@@ -137,6 +137,7 @@ endif + AUDIO_OBJS+= wavcapture.o + OBJS+=$(addprefix audio/, $(AUDIO_OBJS)) + ++OBJS+=keymaps.o + ifdef CONFIG_SDL + OBJS+=sdl.o x_keymap.o + endif +@@ -161,15 +162,17 @@ LIBS+=$(VDE_LIBS) + + cocoa.o: cocoa.m + +-sdl.o: sdl.c keymaps.c sdl_keysym.h ++keymaps.o: keymaps.c keymaps.h ++ ++sdl.o: sdl.c keymaps.h sdl_keysym.h + + sdl.o audio/sdlaudio.o: CFLAGS += $(SDL_CFLAGS) + +-vnc.o: vnc.c keymaps.c sdl_keysym.h vnchextile.h d3des.c d3des.h ++vnc.o: vnc.c keymaps.h sdl_keysym.h vnchextile.h d3des.c d3des.h + + vnc.o: CFLAGS += $(CONFIG_VNC_TLS_CFLAGS) + +-curses.o: curses.c keymaps.c curses_keys.h ++curses.o: curses.c keymaps.h curses_keys.h + + bt-host.o: CFLAGS += $(CONFIG_BLUEZ_CFLAGS) + +diff -r 9ccd7a5b0382 curses.c +--- a/curses.c Thu Feb 19 11:54:21 2009 +0000 ++++ b/curses.c Thu Feb 19 13:15:19 2009 +0000 +@@ -158,7 +158,6 @@ static void curses_cursor_position(Displ + /* generic keyboard conversion */ + + #include "curses_keys.h" +-#include "keymaps.c" + + static kbd_layout_t *kbd_layout = 0; + static int keycode2keysym[CURSES_KEYS]; +@@ -311,7 +310,7 @@ static void curses_keyboard_setup(void) + keyboard_layout = "en-us"; + #endif + if(keyboard_layout) { +- kbd_layout = init_keyboard_layout(keyboard_layout); ++ kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout); + if (!kbd_layout) + exit(1); + } +diff -r 9ccd7a5b0382 curses_keys.h +--- a/curses_keys.h Thu Feb 19 11:54:21 2009 +0000 ++++ b/curses_keys.h Thu Feb 19 13:15:19 2009 +0000 +@@ -21,6 +21,10 @@ + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ ++ ++#include "keymaps.h" ++ ++ + #define KEY_RELEASE 0x80 + #define KEY_MASK 0x7f + #define SHIFT_CODE 0x2a +@@ -239,11 +243,6 @@ static const int curses2keysym[CURSES_KE + + }; + +-typedef struct { +- const char* name; +- int keysym; +-} name2keysym_t; +- + static const name2keysym_t name2keysym[] = { + /* Plain ASCII */ + { "space", 0x020 }, +diff -r 9ccd7a5b0382 keymaps.c +--- a/keymaps.c Thu Feb 19 11:54:21 2009 +0000 ++++ b/keymaps.c Thu Feb 19 13:15:19 2009 +0000 +@@ -22,34 +22,20 @@ + * THE SOFTWARE. + */ + +-static int get_keysym(const char *name) ++#include "keymaps.h" ++#include "sysemu.h" ++ ++static int get_keysym(const name2keysym_t *table, ++ const char *name) + { + const name2keysym_t *p; +- for(p = name2keysym; p->name != NULL; p++) { ++ for(p = table; p->name != NULL; p++) { + if (!strcmp(p->name, name)) + return p->keysym; + } + return 0; + } + +-struct key_range { +- int start; +- int end; +- struct key_range *next; +-}; +- +-#define MAX_NORMAL_KEYCODE 512 +-#define MAX_EXTRA_COUNT 256 +-typedef struct { +- uint16_t keysym2keycode[MAX_NORMAL_KEYCODE]; +- struct { +- int keysym; +- uint16_t keycode; +- } keysym2keycode_extra[MAX_EXTRA_COUNT]; +- int extra_count; +- struct key_range *keypad_range; +- struct key_range *numlock_range; +-} kbd_layout_t; + + static void add_to_key_range(struct key_range **krp, int code) { + struct key_range *kr; +@@ -73,7 +59,8 @@ static void add_to_key_range(struct key_ + } + } + +-static kbd_layout_t *parse_keyboard_layout(const char *language, ++static kbd_layout_t *parse_keyboard_layout(const name2keysym_t *table, ++ const char *language, + kbd_layout_t * k) + { + FILE *f; +@@ -102,7 +89,7 @@ static kbd_layout_t *parse_keyboard_layo + if (!strncmp(line, "map ", 4)) + continue; + if (!strncmp(line, "include ", 8)) { +- parse_keyboard_layout(line + 8, k); ++ parse_keyboard_layout(table, line + 8, k); + } else { + char *end_of_keysym = line; + while (*end_of_keysym != 0 && *end_of_keysym != ' ') +@@ -110,7 +97,7 @@ static kbd_layout_t *parse_keyboard_layo + if (*end_of_keysym) { + int keysym; + *end_of_keysym = 0; +- keysym = get_keysym(line); ++ keysym = get_keysym(table, line); + if (keysym == 0) { + // fprintf(stderr, "Warning: unknown keysym %s\n", line); + } else { +@@ -154,12 +141,14 @@ static kbd_layout_t *parse_keyboard_layo + return k; + } + +-static void *init_keyboard_layout(const char *language) ++ ++void *init_keyboard_layout(const name2keysym_t *table, const char *language) + { +- return parse_keyboard_layout(language, 0); ++ return parse_keyboard_layout(table, language, 0); + } + +-static int keysym2scancode(void *kbd_layout, int keysym) ++ ++int keysym2scancode(void *kbd_layout, int keysym) + { + kbd_layout_t *k = kbd_layout; + if (keysym < MAX_NORMAL_KEYCODE) { +@@ -180,7 +169,7 @@ static int keysym2scancode(void *kbd_lay + return 0; + } + +-static inline int keycode_is_keypad(void *kbd_layout, int keycode) ++int keycode_is_keypad(void *kbd_layout, int keycode) + { + kbd_layout_t *k = kbd_layout; + struct key_range *kr; +@@ -191,7 +180,7 @@ static inline int keycode_is_keypad(void + return 0; + } + +-static inline int keysym_is_numlock(void *kbd_layout, int keysym) ++int keysym_is_numlock(void *kbd_layout, int keysym) + { + kbd_layout_t *k = kbd_layout; + struct key_range *kr; +diff -r 9ccd7a5b0382 keymaps.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/keymaps.h Thu Feb 19 13:15:19 2009 +0000 +@@ -0,0 +1,60 @@ ++/* ++ * QEMU keysym to keycode conversion using rdesktop keymaps ++ * ++ * Copyright (c) 2004 Johannes Schindelin ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++#ifndef __QEMU_KEYMAPS_H__ ++#define __QEMU_KEYMAPS_H__ ++ ++#include "qemu-common.h" ++ ++typedef struct { ++ const char* name; ++ int keysym; ++} name2keysym_t; ++ ++struct key_range { ++ int start; ++ int end; ++ struct key_range *next; ++}; ++ ++#define MAX_NORMAL_KEYCODE 512 ++#define MAX_EXTRA_COUNT 256 ++typedef struct { ++ uint16_t keysym2keycode[MAX_NORMAL_KEYCODE]; ++ struct { ++ int keysym; ++ uint16_t keycode; ++ } keysym2keycode_extra[MAX_EXTRA_COUNT]; ++ int extra_count; ++ struct key_range *keypad_range; ++ struct key_range *numlock_range; ++} kbd_layout_t; ++ ++ ++void *init_keyboard_layout(const name2keysym_t *table, const char *language); ++int keysym2scancode(void *kbd_layout, int keysym); ++int keycode_is_keypad(void *kbd_layout, int keycode); ++int keysym_is_numlock(void *kbd_layout, int keysym); ++ ++#endif /* __QEMU_KEYMAPS_H__ */ +diff -r 9ccd7a5b0382 sdl.c +--- a/sdl.c Thu Feb 19 11:54:21 2009 +0000 ++++ b/sdl.c Thu Feb 19 13:15:19 2009 +0000 +@@ -107,7 +107,6 @@ static void sdl_resize(DisplayState *ds) + /* generic keyboard conversion */ + + #include "sdl_keysym.h" +-#include "keymaps.c" + + static kbd_layout_t *kbd_layout = NULL; + +@@ -623,7 +622,7 @@ void sdl_display_init(DisplayState *ds, + keyboard_layout = "en-us"; + #endif + if(keyboard_layout) { +- kbd_layout = init_keyboard_layout(keyboard_layout); ++ kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout); + if (!kbd_layout) + exit(1); + } +diff -r 9ccd7a5b0382 sdl_keysym.h +--- a/sdl_keysym.h Thu Feb 19 11:54:21 2009 +0000 ++++ b/sdl_keysym.h Thu Feb 19 13:15:19 2009 +0000 +@@ -1,7 +1,6 @@ +-typedef struct { +- const char* name; +- int keysym; +-} name2keysym_t; ++ ++#include "keymaps.h" ++ + static const name2keysym_t name2keysym[]={ + /* ascii */ + { "space", 0x020}, +diff -r 9ccd7a5b0382 vnc.c +--- a/vnc.c Thu Feb 19 11:54:21 2009 +0000 ++++ b/vnc.c Thu Feb 19 13:15:19 2009 +0000 +@@ -35,7 +35,6 @@ + + #include "vnc.h" + #include "vnc_keysym.h" +-#include "keymaps.c" + #include "d3des.h" + + #ifdef CONFIG_VNC_TLS +@@ -2420,9 +2419,9 @@ void vnc_display_init(DisplayState *ds) + vs->ds = ds; + + if (keyboard_layout) +- vs->kbd_layout = init_keyboard_layout(keyboard_layout); ++ vs->kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout); + else +- vs->kbd_layout = init_keyboard_layout("en-us"); ++ vs->kbd_layout = init_keyboard_layout(name2keysym, "en-us"); + + if (!vs->kbd_layout) + exit(1); +diff -r 9ccd7a5b0382 vnc_keysym.h +--- a/vnc_keysym.h Thu Feb 19 11:54:21 2009 +0000 ++++ b/vnc_keysym.h Thu Feb 19 13:15:19 2009 +0000 +@@ -1,7 +1,6 @@ +-typedef struct { +- const char* name; +- int keysym; +-} name2keysym_t; ++ ++#include "keymaps.h" ++ + static const name2keysym_t name2keysym[]={ + /* ascii */ + { "space", 0x020}, diff --git a/qemu-sasl-04-vnc-struct.patch b/qemu-sasl-04-vnc-struct.patch new file mode 100644 index 0000000..a5937c6 --- /dev/null +++ b/qemu-sasl-04-vnc-struct.patch @@ -0,0 +1,313 @@ +This patch moves the definitions of VncState and VncDisplay structs +out into a vnc.h header file. This is to allow the code for TLS +and SASL auth mechanisms to be moved out of the main vnc.c file. + + + vnc.c | 109 ------------------------------------------------ + vnc.h | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 148 insertions(+), 110 deletions(-) + + Signed-off-by: Daniel P. Berrange + +diff -r 216ec7d1e30f vnc.c +--- a/vnc.c Thu Feb 19 13:15:19 2009 +0000 ++++ b/vnc.c Thu Feb 19 13:25:09 2009 +0000 +@@ -3,6 +3,7 @@ + * + * Copyright (C) 2006 Anthony Liguori + * Copyright (C) 2006 Fabrice Bellard ++ * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal +@@ -23,25 +24,16 @@ + * THE SOFTWARE. + */ + +-#include "qemu-common.h" +-#include "console.h" ++#include "vnc.h" + #include "sysemu.h" + #include "qemu_socket.h" + #include "qemu-timer.h" +-#include "audio/audio.h" +-#include + + #define VNC_REFRESH_INTERVAL (1000 / 30) + +-#include "vnc.h" + #include "vnc_keysym.h" + #include "d3des.h" + +-#ifdef CONFIG_VNC_TLS +-#include +-#include +-#endif /* CONFIG_VNC_TLS */ +- + // #define _VNC_DEBUG 1 + + #ifdef _VNC_DEBUG +@@ -64,103 +56,6 @@ static void vnc_debug_gnutls_log(int lev + } \ + } + +-typedef struct Buffer +-{ +- size_t capacity; +- size_t offset; +- uint8_t *buffer; +-} Buffer; +- +-typedef struct VncState VncState; +- +-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, +- void *last_bg, +- void *last_fg, +- int *has_bg, int *has_fg); +- +-#define VNC_MAX_WIDTH 2048 +-#define VNC_MAX_HEIGHT 2048 +-#define VNC_DIRTY_WORDS (VNC_MAX_WIDTH / (16 * 32)) +- +-#define VNC_AUTH_CHALLENGE_SIZE 16 +- +-typedef struct VncDisplay VncDisplay; +- +-struct VncDisplay +-{ +- int lsock; +- DisplayState *ds; +- VncState *clients; +- kbd_layout_t *kbd_layout; +- +- char *display; +- char *password; +- int auth; +-#ifdef CONFIG_VNC_TLS +- int subauth; +- int x509verify; +- +- char *x509cacert; +- char *x509cacrl; +- char *x509cert; +- char *x509key; +-#endif +-}; +- +-struct VncState +-{ +- QEMUTimer *timer; +- int csock; +- DisplayState *ds; +- VncDisplay *vd; +- int need_update; +- uint32_t dirty_row[VNC_MAX_HEIGHT][VNC_DIRTY_WORDS]; +- char *old_data; +- uint32_t features; +- int absolute; +- int last_x; +- int last_y; +- +- uint32_t vnc_encoding; +- uint8_t tight_quality; +- uint8_t tight_compression; +- +- int major; +- int minor; +- +- char challenge[VNC_AUTH_CHALLENGE_SIZE]; +- +-#ifdef CONFIG_VNC_TLS +- int wiremode; +- gnutls_session_t tls_session; +-#endif +- +- Buffer output; +- Buffer input; +- /* current output mode information */ +- VncWritePixels *write_pixels; +- VncSendHextileTile *send_hextile_tile; +- DisplaySurface clientds, serverds; +- +- CaptureVoiceOut *audio_cap; +- struct audsettings as; +- +- VncReadEvent *read_handler; +- size_t read_handler_expect; +- /* input */ +- uint8_t modifiers_state[256]; +- +- Buffer zlib; +- Buffer zlib_tmp; +- z_stream zlib_stream[4]; +- +- VncState *next; +-}; + + static VncDisplay *vnc_display; /* needed for info vnc */ + static DisplayChangeListener *dcl; +diff -r 216ec7d1e30f vnc.h +--- a/vnc.h Thu Feb 19 13:15:19 2009 +0000 ++++ b/vnc.h Thu Feb 19 13:25:09 2009 +0000 +@@ -1,5 +1,148 @@ +-#ifndef __VNCTIGHT_H +-#define __VNCTIGHT_H ++/* ++ * QEMU VNC display driver ++ * ++ * Copyright (C) 2006 Anthony Liguori ++ * Copyright (C) 2006 Fabrice Bellard ++ * Copyright (C) 2009 Red Hat, Inc ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++#ifndef __QEMU_VNC_H ++#define __QEMU_VNC_H ++ ++#include "qemu-common.h" ++#include "console.h" ++#include "audio/audio.h" ++#include ++ ++#ifdef CONFIG_VNC_TLS ++#include ++#include ++#endif /* CONFIG_VNC_TLS */ ++ ++#include "keymaps.h" ++ ++/***************************************************************************** ++ * ++ * Core data structures ++ * ++ *****************************************************************************/ ++ ++typedef struct Buffer ++{ ++ size_t capacity; ++ size_t offset; ++ uint8_t *buffer; ++} Buffer; ++ ++typedef struct VncState VncState; ++ ++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, ++ void *last_bg, ++ void *last_fg, ++ int *has_bg, int *has_fg); ++ ++#define VNC_MAX_WIDTH 2048 ++#define VNC_MAX_HEIGHT 2048 ++#define VNC_DIRTY_WORDS (VNC_MAX_WIDTH / (16 * 32)) ++ ++#define VNC_AUTH_CHALLENGE_SIZE 16 ++ ++typedef struct VncDisplay VncDisplay; ++ ++struct VncDisplay ++{ ++ int lsock; ++ DisplayState *ds; ++ VncState *clients; ++ kbd_layout_t *kbd_layout; ++ ++ char *display; ++ char *password; ++ int auth; ++#ifdef CONFIG_VNC_TLS ++ int subauth; ++ int x509verify; ++ ++ char *x509cacert; ++ char *x509cacrl; ++ char *x509cert; ++ char *x509key; ++#endif ++}; ++ ++struct VncState ++{ ++ QEMUTimer *timer; ++ int csock; ++ DisplayState *ds; ++ VncDisplay *vd; ++ int need_update; ++ uint32_t dirty_row[VNC_MAX_HEIGHT][VNC_DIRTY_WORDS]; ++ char *old_data; ++ uint32_t features; ++ int absolute; ++ int last_x; ++ int last_y; ++ ++ uint32_t vnc_encoding; ++ uint8_t tight_quality; ++ uint8_t tight_compression; ++ ++ int major; ++ int minor; ++ ++ char challenge[VNC_AUTH_CHALLENGE_SIZE]; ++ ++#ifdef CONFIG_VNC_TLS ++ int wiremode; ++ gnutls_session_t tls_session; ++#endif ++ ++ Buffer output; ++ Buffer input; ++ /* current output mode information */ ++ VncWritePixels *write_pixels; ++ VncSendHextileTile *send_hextile_tile; ++ DisplaySurface clientds, serverds; ++ ++ CaptureVoiceOut *audio_cap; ++ struct audsettings as; ++ ++ VncReadEvent *read_handler; ++ size_t read_handler_expect; ++ /* input */ ++ uint8_t modifiers_state[256]; ++ ++ Buffer zlib; ++ Buffer zlib_tmp; ++ z_stream zlib_stream[4]; ++ ++ VncState *next; ++}; ++ + + /***************************************************************************** + * +@@ -111,4 +254,4 @@ enum { + #define VNC_FEATURE_ZLIB_MASK (1 << VNC_FEATURE_ZLIB) + #define VNC_FEATURE_COPYRECT_MASK (1 << VNC_FEATURE_COPYRECT) + +-#endif /* __VNCTIGHT_H */ ++#endif /* __QEMU_VNC_H */ diff --git a/qemu-sasl-05-vnc-tls-vencrypt.patch b/qemu-sasl-05-vnc-tls-vencrypt.patch new file mode 100644 index 0000000..ab110c4 --- /dev/null +++ b/qemu-sasl-05-vnc-tls-vencrypt.patch @@ -0,0 +1,1673 @@ +This patch refactors the existing TLS code to make the main VNC code +more managable. The code moves to two new files + + - vnc-tls.c: generic helpers for TLS handshake & credential setup + - vnc-auth-vencrypt.c: the actual VNC TLS authentication mechanism. + +The reason for this split is that there are other TLS based auth +mechanisms which we may like to use in the future. These can all +share the same vnc-tls.c routines. In addition this will facilitate +anyone who may want to port the vnc-tls.c file to allow for choice +of GNUTLS & NSS for impl. + +The TLS state is moved out of the VncState struct, and into a separate +VncStateTLS struct, defined in vnc-tls.h. This is then referenced from +the main VncState. End size of the struct is the same, but it keeps +things a little more managable. + +The vnc.h file gains a bunch more function prototypes, for functions +in vnc.c that were previously static, but now need to be accessed +from the separate auth code files. + +The only TLS related code still in the main vl.c is the command line +argument handling / setup, and the low level I/O routines calling +gnutls_send/recv. + + + Makefile | 11 + b/vnc-auth-vencrypt.c | 167 ++++++++++++++ + b/vnc-auth-vencrypt.h | 33 ++ + b/vnc-tls.c | 414 +++++++++++++++++++++++++++++++++++ + b/vnc-tls.h | 70 ++++++ + vnc.c | 581 +++----------------------------------------------- + vnc.h | 76 ++++-- + 7 files changed, 780 insertions(+), 572 deletions(-) + + Signed-off-by: Daniel P. Berrange + +diff -r dfa890dd7417 Makefile +--- a/Makefile Mon Feb 23 13:05:49 2009 +0000 ++++ b/Makefile Mon Feb 23 13:06:22 2009 +0000 +@@ -145,6 +145,9 @@ ifdef CONFIG_CURSES + OBJS+=curses.o + endif + OBJS+=vnc.o d3des.o ++ifdef CONFIG_VNC_TLS ++OBJS+=vnc-tls.o vnc-auth-vencrypt.o ++endif + + ifdef CONFIG_COCOA + OBJS+=cocoa.o +@@ -168,10 +171,16 @@ sdl.o: sdl.c keymaps.h sdl_keysym.h + + sdl.o audio/sdlaudio.o: CFLAGS += $(SDL_CFLAGS) + +-vnc.o: vnc.c keymaps.h sdl_keysym.h vnchextile.h d3des.c d3des.h ++vnc.h: vnc-tls.h vnc-auth-vencrypt.h keymaps.h ++ ++vnc.o: vnc.c vnc.h vnc_keysym.h vnchextile.h d3des.c d3des.h + + vnc.o: CFLAGS += $(CONFIG_VNC_TLS_CFLAGS) + ++vnc-tls.o: vnc-tls.c vnc.h ++ ++vnc-auth-vencrypt.o: vnc-auth-vencrypt.c vnc.h ++ + curses.o: curses.c keymaps.h curses_keys.h + + bt-host.o: CFLAGS += $(CONFIG_BLUEZ_CFLAGS) +diff -r dfa890dd7417 vnc-auth-vencrypt.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/vnc-auth-vencrypt.c Mon Feb 23 13:06:22 2009 +0000 +@@ -0,0 +1,167 @@ ++/* ++ * QEMU VNC display driver: VeNCrypt authentication setup ++ * ++ * Copyright (C) 2006 Anthony Liguori ++ * Copyright (C) 2006 Fabrice Bellard ++ * Copyright (C) 2009 Red Hat, Inc ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++#include "vnc.h" ++ ++ ++static void start_auth_vencrypt_subauth(VncState *vs) ++{ ++ switch (vs->vd->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 */ ++ start_client_init(vs); ++ break; ++ ++ case VNC_AUTH_VENCRYPT_TLSVNC: ++ case VNC_AUTH_VENCRYPT_X509VNC: ++ VNC_DEBUG("Start TLS auth VNC\n"); ++ start_auth_vnc(vs); ++ break; ++ ++ default: /* Should not be possible, but just in case */ ++ VNC_DEBUG("Reject auth %d\n", vs->vd->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); ++ } ++} ++ ++static void vnc_tls_handshake_io(void *opaque); ++ ++static int vnc_start_vencrypt_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_tls_handshake_io, NULL, vs); ++ else ++ qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs); ++ return 0; ++ } ++ VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret)); ++ vnc_client_error(vs); ++ return -1; ++ } ++ ++ if (vs->vd->tls.x509verify) { ++ if (vnc_tls_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->tls.wiremode = VNC_WIREMODE_TLS; ++ qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs); ++ ++ start_auth_vencrypt_subauth(vs); ++ ++ return 0; ++} ++ ++static void vnc_tls_handshake_io(void *opaque) { ++ struct VncState *vs = (struct VncState *)opaque; ++ ++ VNC_DEBUG("Handshake IO continue\n"); ++ vnc_start_vencrypt_handshake(vs); ++} ++ ++ ++ ++#define NEED_X509_AUTH(vs) \ ++ ((vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ ++ (vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ ++ (vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509PLAIN) ++ ++ ++static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len) ++{ ++ int auth = read_u32(data, 0); ++ ++ if (auth != vs->vd->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, setting up TLS for handshake\n", auth); ++ vnc_write_u8(vs, 1); /* Accept auth */ ++ vnc_flush(vs); ++ ++ if (vnc_tls_client_setup(vs, NEED_X509_AUTH(vs)) < 0) { ++ VNC_DEBUG("Failed to setup TLS\n"); ++ return 0; ++ } ++ ++ VNC_DEBUG("Start TLS VeNCrypt handshake process\n"); ++ if (vnc_start_vencrypt_handshake(vs) < 0) { ++ VNC_DEBUG("Failed to start TLS handshake\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->vd->subauth); ++ vnc_write_u8(vs, 0); /* Accept version */ ++ vnc_write_u8(vs, 1); /* Number of sub-auths */ ++ vnc_write_u32(vs, vs->vd->subauth); /* The supported auth */ ++ vnc_flush(vs); ++ vnc_read_when(vs, protocol_client_vencrypt_auth, 4); ++ } ++ return 0; ++} ++ ++ ++void 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); ++} ++ +diff -r dfa890dd7417 vnc-auth-vencrypt.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/vnc-auth-vencrypt.h Mon Feb 23 13:06:22 2009 +0000 +@@ -0,0 +1,33 @@ ++/* ++ * QEMU VNC display driver ++ * ++ * Copyright (C) 2006 Anthony Liguori ++ * Copyright (C) 2006 Fabrice Bellard ++ * Copyright (C) 2009 Red Hat, Inc ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++ ++#ifndef __QEMU_VNC_AUTH_VENCRYPT_H__ ++#define __QEMU_VNC_AUTH_VENCRYPT_H__ ++ ++void start_auth_vencrypt(VncState *vs); ++ ++#endif /* __QEMU_VNC_AUTH_VENCRYPT_H__ */ +diff -r dfa890dd7417 vnc-tls.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/vnc-tls.c Mon Feb 23 13:06:22 2009 +0000 +@@ -0,0 +1,414 @@ ++/* ++ * QEMU VNC display driver: TLS helpers ++ * ++ * Copyright (C) 2006 Anthony Liguori ++ * Copyright (C) 2006 Fabrice Bellard ++ * Copyright (C) 2009 Red Hat, Inc ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++#include "vnc.h" ++#include "qemu_socket.h" ++ ++#if defined(_VNC_DEBUG) && _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 /* defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 */ ++ ++ ++#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 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; ++} ++ ++ ++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(VncDisplay *vd) ++{ ++ gnutls_certificate_credentials_t x509_cred; ++ int ret; ++ ++ if (!vd->tls.x509cacert) { ++ VNC_DEBUG("No CA x509 certificate specified\n"); ++ return NULL; ++ } ++ if (!vd->tls.x509cert) { ++ VNC_DEBUG("No server x509 certificate specified\n"); ++ return NULL; ++ } ++ if (!vd->tls.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, ++ vd->tls.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, ++ vd->tls.x509cert, ++ vd->tls.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 (vd->tls.x509cacrl) { ++ if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, ++ vd->tls.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; ++} ++ ++ ++int vnc_tls_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; ++} ++ ++ ++int vnc_tls_client_setup(struct VncState *vs, ++ int needX509Creds) { ++ 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, needX509Creds ? 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 (needX509Creds) { ++ gnutls_certificate_server_credentials x509_cred = vnc_tls_initialize_x509_cred(vs->vd); ++ 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->vd->tls.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); ++ } ++ return 0; ++} ++ ++ ++void vnc_tls_client_cleanup(struct VncState *vs) ++{ ++ if (vs->tls.session) { ++ gnutls_deinit(vs->tls.session); ++ vs->tls.session = NULL; ++ } ++ vs->tls.wiremode = VNC_WIREMODE_CLEAR; ++} ++ ++ ++ ++static int vnc_set_x509_credential(VncDisplay *vd, ++ const char *certdir, ++ const char *filename, ++ char **cred, ++ int ignoreMissing) ++{ ++ struct stat sb; ++ ++ if (*cred) { ++ qemu_free(*cred); ++ *cred = NULL; ++ } ++ ++ *cred = qemu_malloc(strlen(certdir) + strlen(filename) + 2); ++ ++ 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; ++} ++ ++ ++#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" ++ ++ ++int vnc_tls_set_x509_creds_dir(VncDisplay *vd, ++ const char *certdir) ++{ ++ if (vnc_set_x509_credential(vd, certdir, X509_CA_CERT_FILE, &vd->tls.x509cacert, 0) < 0) ++ goto cleanup; ++ if (vnc_set_x509_credential(vd, certdir, X509_CA_CRL_FILE, &vd->tls.x509cacrl, 1) < 0) ++ goto cleanup; ++ if (vnc_set_x509_credential(vd, certdir, X509_SERVER_CERT_FILE, &vd->tls.x509cert, 0) < 0) ++ goto cleanup; ++ if (vnc_set_x509_credential(vd, certdir, X509_SERVER_KEY_FILE, &vd->tls.x509key, 0) < 0) ++ goto cleanup; ++ ++ return 0; ++ ++ cleanup: ++ qemu_free(vd->tls.x509cacert); ++ qemu_free(vd->tls.x509cacrl); ++ qemu_free(vd->tls.x509cert); ++ qemu_free(vd->tls.x509key); ++ vd->tls.x509cacert = vd->tls.x509cacrl = vd->tls.x509cert = vd->tls.x509key = NULL; ++ return -1; ++} ++ +diff -r dfa890dd7417 vnc-tls.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/vnc-tls.h Mon Feb 23 13:06:22 2009 +0000 +@@ -0,0 +1,70 @@ ++/* ++ * QEMU VNC display driver. TLS helpers ++ * ++ * Copyright (C) 2006 Anthony Liguori ++ * Copyright (C) 2006 Fabrice Bellard ++ * Copyright (C) 2009 Red Hat, Inc ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++ ++#ifndef __QEMU_VNC_TLS_H__ ++#define __QEMU_VNC_TLS_H__ ++ ++#include ++#include ++ ++enum { ++ VNC_WIREMODE_CLEAR, ++ VNC_WIREMODE_TLS, ++}; ++ ++typedef struct VncDisplayTLS VncDisplayTLS; ++typedef struct VncStateTLS VncStateTLS; ++ ++/* Server state */ ++struct VncDisplayTLS { ++ int x509verify; /* Non-zero if server requests & validates client cert */ ++ ++ /* Paths to x509 certs/keys */ ++ char *x509cacert; ++ char *x509cacrl; ++ char *x509cert; ++ char *x509key; ++}; ++ ++/* Per client state */ ++struct VncStateTLS { ++ /* Whether data is being TLS encrypted yet */ ++ int wiremode; ++ gnutls_session_t session; ++}; ++ ++int vnc_tls_client_setup(VncState *vs, int x509Creds); ++void vnc_tls_client_cleanup(VncState *vs); ++ ++int vnc_tls_validate_certificate(VncState *vs); ++ ++int vnc_tls_set_x509_creds_dir(VncDisplay *vd, ++ const char *path); ++ ++ ++#endif /* __QEMU_VNC_TLS_H__ */ ++ +diff -r dfa890dd7417 vnc.c +--- a/vnc.c Mon Feb 23 13:05:49 2009 +0000 ++++ b/vnc.c Mon Feb 23 13:06:22 2009 +0000 +@@ -34,21 +34,6 @@ + #include "vnc_keysym.h" + #include "d3des.h" + +-// #define _VNC_DEBUG 1 +- +-#ifdef _VNC_DEBUG +-#define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +- +-#if defined(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 +- + #define count_bits(c, v) { \ + for (c = 0; v; v >>= 1) \ + { \ +@@ -204,14 +189,7 @@ static inline uint32_t vnc_has_feature(V + 3) resolutions > 1024 + */ + +-static void vnc_write(VncState *vs, const void *data, size_t len); +-static void vnc_write_u32(VncState *vs, uint32_t value); +-static void vnc_write_s32(VncState *vs, int32_t value); +-static void vnc_write_u16(VncState *vs, uint16_t value); +-static void vnc_write_u8(VncState *vs, uint8_t value); +-static void vnc_flush(VncState *vs); + static void vnc_update_client(void *opaque); +-static void vnc_client_read(void *opaque); + + static void vnc_colordepth(VncState *vs); + +@@ -867,10 +845,7 @@ static int vnc_client_io_error(VncState + if (vs->input.buffer) qemu_free(vs->input.buffer); + if (vs->output.buffer) qemu_free(vs->output.buffer); + #ifdef CONFIG_VNC_TLS +- if (vs->tls_session) { +- gnutls_deinit(vs->tls_session); +- vs->tls_session = NULL; +- } ++ vnc_tls_client_cleanup(vs); + #endif /* CONFIG_VNC_TLS */ + audio_del(vs); + +@@ -896,19 +871,20 @@ static int vnc_client_io_error(VncState + return ret; + } + +-static void vnc_client_error(VncState *vs) ++ ++void vnc_client_error(VncState *vs) + { + vnc_client_io_error(vs, -1, EINVAL); + } + +-static void vnc_client_write(void *opaque) ++void vnc_client_write(void *opaque) + { + long ret; + VncState *vs = opaque; + + #ifdef CONFIG_VNC_TLS +- if (vs->tls_session) { +- ret = gnutls_write(vs->tls_session, vs->output.buffer, vs->output.offset); ++ 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; +@@ -931,13 +907,13 @@ static void vnc_client_write(void *opaqu + } + } + +-static void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) ++void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) + { + vs->read_handler = func; + vs->read_handler_expect = expecting; + } + +-static void vnc_client_read(void *opaque) ++void vnc_client_read(void *opaque) + { + VncState *vs = opaque; + long ret; +@@ -945,8 +921,8 @@ static void vnc_client_read(void *opaque + buffer_reserve(&vs->input, 4096); + + #ifdef CONFIG_VNC_TLS +- if (vs->tls_session) { +- ret = gnutls_read(vs->tls_session, buffer_end(&vs->input), 4096); ++ 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; +@@ -980,7 +956,7 @@ static void vnc_client_read(void *opaque + } + } + +-static void vnc_write(VncState *vs, const void *data, size_t len) ++void vnc_write(VncState *vs, const void *data, size_t len) + { + buffer_reserve(&vs->output, len); + +@@ -991,12 +967,12 @@ static void vnc_write(VncState *vs, cons + buffer_append(&vs->output, data, len); + } + +-static void vnc_write_s32(VncState *vs, int32_t value) ++void vnc_write_s32(VncState *vs, int32_t value) + { + vnc_write_u32(vs, *(uint32_t *)&value); + } + +-static void vnc_write_u32(VncState *vs, uint32_t value) ++void vnc_write_u32(VncState *vs, uint32_t value) + { + uint8_t buf[4]; + +@@ -1008,7 +984,7 @@ static void vnc_write_u32(VncState *vs, + vnc_write(vs, buf, 4); + } + +-static void vnc_write_u16(VncState *vs, uint16_t value) ++void vnc_write_u16(VncState *vs, uint16_t value) + { + uint8_t buf[2]; + +@@ -1018,74 +994,39 @@ static void vnc_write_u16(VncState *vs, + vnc_write(vs, buf, 2); + } + +-static void vnc_write_u8(VncState *vs, uint8_t value) ++void vnc_write_u8(VncState *vs, uint8_t value) + { + vnc_write(vs, (char *)&value, 1); + } + +-static void vnc_flush(VncState *vs) ++void vnc_flush(VncState *vs) + { + if (vs->output.offset) + vnc_client_write(vs); + } + +-static uint8_t read_u8(uint8_t *data, size_t offset) ++uint8_t read_u8(uint8_t *data, size_t offset) + { + return data[offset]; + } + +-static uint16_t read_u16(uint8_t *data, size_t offset) ++uint16_t read_u16(uint8_t *data, size_t offset) + { + return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF); + } + +-static int32_t read_s32(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]); + } + +-static uint32_t read_u32(uint8_t *data, size_t offset) ++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]); + } + +-#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) + { + } +@@ -1668,6 +1609,11 @@ static int protocol_client_init(VncState + return 0; + } + ++void start_client_init(VncState *vs) ++{ ++ vnc_read_when(vs, protocol_client_init, 1); ++} ++ + static void make_challenge(VncState *vs) + { + int i; +@@ -1723,12 +1669,12 @@ static int protocol_client_auth_vnc(VncS + vnc_write_u32(vs, 0); /* Accept auth */ + vnc_flush(vs); + +- vnc_read_when(vs, protocol_client_init, 1); ++ start_client_init(vs); + } + return 0; + } + +-static int start_auth_vnc(VncState *vs) ++void start_auth_vnc(VncState *vs) + { + make_challenge(vs); + /* Send client a 'random' challenge */ +@@ -1736,411 +1682,9 @@ static int start_auth_vnc(VncState *vs) + vnc_flush(vs); + + vnc_read_when(vs, protocol_client_auth_vnc, sizeof(vs->challenge)); +- return 0; + } + + +-#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->vd->x509cacert) { +- VNC_DEBUG("No CA x509 certificate specified\n"); +- return NULL; +- } +- if (!vs->vd->x509cert) { +- VNC_DEBUG("No server x509 certificate specified\n"); +- return NULL; +- } +- if (!vs->vd->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->vd->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->vd->x509cert, +- vs->vd->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->vd->x509cacrl) { +- if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, +- vs->vd->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->vd->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->vd->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->vd->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)->vd->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ +- (vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ +- (vs)->vd->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->vd->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->vd->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; +- } +- } +- 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->vd->subauth); +- vnc_write_u8(vs, 0); /* Accept version */ +- vnc_write_u8(vs, 1); /* Number of sub-auths */ +- vnc_write_u32(vs, vs->vd->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 +@@ -2163,17 +1707,19 @@ static int protocol_client_auth(VncState + vnc_write_u32(vs, 0); /* Accept auth completion */ + vnc_flush(vs); + } +- vnc_read_when(vs, protocol_client_init, 1); ++ start_client_init(vs); + break; + + case VNC_AUTH_VNC: + VNC_DEBUG("Start VNC auth\n"); +- return start_auth_vnc(vs); ++ start_auth_vnc(vs); ++ break; + + #ifdef CONFIG_VNC_TLS + case VNC_AUTH_VENCRYPT: + VNC_DEBUG("Accept VeNCrypt auth\n");; +- return start_auth_vencrypt(vs); ++ start_auth_vencrypt(vs); ++ break; + #endif /* CONFIG_VNC_TLS */ + + default: /* Should not be possible, but just in case */ +@@ -2226,7 +1772,7 @@ static int protocol_version(VncState *vs + VNC_DEBUG("Tell client auth none\n"); + vnc_write_u32(vs, vs->vd->auth); + vnc_flush(vs); +- vnc_read_when(vs, protocol_client_init, 1); ++ 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); +@@ -2328,61 +1874,6 @@ void vnc_display_init(DisplayState *ds) + register_displaychangelistener(ds, dcl); + } + +-#ifdef CONFIG_VNC_TLS +-static int vnc_set_x509_credential(VncDisplay *vs, +- const char *certdir, +- const char *filename, +- char **cred, +- int ignoreMissing) +-{ +- struct stat sb; +- +- if (*cred) { +- qemu_free(*cred); +- *cred = NULL; +- } +- +- *cred = qemu_malloc(strlen(certdir) + strlen(filename) + 2); +- +- 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(VncDisplay *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) + { +@@ -2402,7 +1893,7 @@ void vnc_display_close(DisplayState *ds) + vs->auth = VNC_AUTH_INVALID; + #ifdef CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; +- vs->x509verify = 0; ++ vs->tls.x509verify = 0; + #endif + } + +@@ -2458,7 +1949,7 @@ int vnc_display_open(DisplayState *ds, c + char *start, *end; + x509 = 1; /* Require x509 certificates */ + if (strncmp(options, "x509verify", 10) == 0) +- vs->x509verify = 1; /* ...and verify client certs */ ++ vs->tls.x509verify = 1; /* ...and verify client certs */ + + /* Now check for 'x509=/some/path' postfix + * and use that to setup x509 certificate/key paths */ +@@ -2469,7 +1960,7 @@ int vnc_display_open(DisplayState *ds, c + char *path = qemu_strndup(start + 1, len); + + VNC_DEBUG("Trying certificate path '%s'\n", path); +- if (vnc_set_x509_credential_dir(vs, path) < 0) { ++ 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); +diff -r dfa890dd7417 vnc.h +--- a/vnc.h Mon Feb 23 13:05:49 2009 +0000 ++++ b/vnc.h Mon Feb 23 13:06:22 2009 +0000 +@@ -32,12 +32,15 @@ + #include "audio/audio.h" + #include + +-#ifdef CONFIG_VNC_TLS +-#include +-#include +-#endif /* CONFIG_VNC_TLS */ ++#include "keymaps.h" + +-#include "keymaps.h" ++// #define _VNC_DEBUG 1 ++ ++#ifdef _VNC_DEBUG ++#define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) ++#else ++#define VNC_DEBUG(fmt, ...) do { } while (0) ++#endif + + /***************************************************************************** + * +@@ -72,6 +75,11 @@ typedef void VncSendHextileTile(VncState + + typedef struct VncDisplay VncDisplay; + ++#ifdef CONFIG_VNC_TLS ++#include "vnc-tls.h" ++#include "vnc-auth-vencrypt.h" ++#endif ++ + struct VncDisplay + { + int lsock; +@@ -83,13 +91,8 @@ struct VncDisplay + char *password; + int auth; + #ifdef CONFIG_VNC_TLS +- int subauth; +- int x509verify; +- +- char *x509cacert; +- char *x509cacrl; +- char *x509cert; +- char *x509key; ++ int subauth; /* Used by VeNCrypt */ ++ VncDisplayTLS tls; + #endif + }; + +@@ -117,8 +120,7 @@ struct VncState + char challenge[VNC_AUTH_CHALLENGE_SIZE]; + + #ifdef CONFIG_VNC_TLS +- int wiremode; +- gnutls_session_t tls_session; ++ VncStateTLS tls; + #endif + + Buffer output; +@@ -162,12 +164,6 @@ 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, +@@ -178,12 +174,6 @@ enum { + 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 */ + + /***************************************************************************** + * +@@ -254,4 +244,38 @@ enum { + #define VNC_FEATURE_ZLIB_MASK (1 << VNC_FEATURE_ZLIB) + #define VNC_FEATURE_COPYRECT_MASK (1 << VNC_FEATURE_COPYRECT) + ++ ++/***************************************************************************** ++ * ++ * Internal APIs ++ * ++ *****************************************************************************/ ++ ++/* Event loop functions */ ++void vnc_client_read(void *opaque); ++void vnc_client_write(void *opaque); ++ ++ ++/* Protocol I/O functions */ ++void vnc_write(VncState *vs, const void *data, size_t len); ++void vnc_write_u32(VncState *vs, uint32_t value); ++void vnc_write_s32(VncState *vs, int32_t value); ++void vnc_write_u16(VncState *vs, uint16_t value); ++void vnc_write_u8(VncState *vs, uint8_t value); ++void vnc_flush(VncState *vs); ++void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting); ++ ++ ++/* Buffer I/O functions */ ++uint8_t read_u8(uint8_t *data, size_t offset); ++uint16_t read_u16(uint8_t *data, size_t offset); ++int32_t read_s32(uint8_t *data, size_t offset); ++uint32_t read_u32(uint8_t *data, size_t offset); ++ ++/* Protocol stage functions */ ++void vnc_client_error(VncState *vs); ++ ++void start_client_init(VncState *vs); ++void start_auth_vnc(VncState *vs); ++ + #endif /* __QEMU_VNC_H */ diff --git a/qemu-sasl-06-vnc-sasl.patch b/qemu-sasl-06-vnc-sasl.patch new file mode 100644 index 0000000..23fe780 --- /dev/null +++ b/qemu-sasl-06-vnc-sasl.patch @@ -0,0 +1,1594 @@ +This patch adds the new SASL authentication protocol to the VNC server. + +It is enabled by setting the 'sasl' flag when launching VNC. SASL can +optionally provide encryption via its SSF layer, if a suitable mechanism +is configured (eg, GSSAPI/Kerberos, or Digest-MD5). If an SSF layer is +not available, then it should be combined with the x509 VNC authentication +protocol which provides encryption. + +eg, if using GSSAPI + + qemu -vnc localhost:1,sasl + +eg if using TLS/x509 for encryption + + qemu -vnc localhost:1,sasl,tls,x509 + + +By default the Cyrus SASL library will look for its configuration in +the file /etc/sasl2/qemu.conf. For non-root users, this can be overridden +by setting the SASL_CONF_PATH environment variable, eg to make it look in +$HOME/.sasl2. NB unprivileged users may not have access to the full range +of SASL mechanisms, since some of them require some administrative privileges +to configure. The patch includes an example SASL configuration file which +illustrates config for GSSAPI and Digest-MD5, though it should be noted that +the latter is not really considered secure any more. + +Most of the SASL authentication code is located in a separate source file, +vnc-auth-sasl.c. The main vnc.c file only contains minimal integration +glue, specifically parsing of command line flags / setup, and calls to +start the SASL auth process, to do encoding/decoding for data. + +There are several possible stacks for reading & writing of data, depending +on the combo of VNC authentication methods in use + + - Clear. read/write straight to socket + - TLS. read/write via GNUTLS helpers + - SASL. encode/decode via SASL SSF layer, then read/write to socket + - SASL+TLS. encode/decode via SASL SSF layer, then read/write via GNUTLS + +Hence, the vnc_client_read & vnc_client_write methods have been refactored +a little. + + vnc_client_read: main entry point for reading, calls either + + - vnc_client_read_plain reading, with no intermediate decoding + - vnc_client_read_sasl reading, with SASL SSF decoding + + These two methods, then call vnc_client_read_buf(). This decides + whether to write to the socket directly or write via GNUTLS. + +The situation is the same for writing data. More extensive comments +have been added in the code / patch. The vnc_client_read_sasl and +vnc_client_write_sasl method implementations live in the separate +vnc-auth-sasl.c file. + +The state required for the SASL auth mechanism is kept in a separate +VncStateSASL struct, defined in vnc-auth-sasl.h and included in the +main VncState. + +The configure script probes for SASL and automatically enables it +if found, unless --disable-vnc-sasl was given to override it. + + + Makefile | 7 + Makefile.target | 5 + b/qemu.sasl | 34 ++ + b/vnc-auth-sasl.c | 626 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + b/vnc-auth-sasl.h | 67 +++++ + configure | 34 ++ + qemu-doc.texi | 97 ++++++++ + vnc-auth-vencrypt.c | 12 + vnc.c | 249 ++++++++++++++++++-- + vnc.h | 31 ++ + 10 files changed, 1129 insertions(+), 33 deletions(-) + + Signed-off-by: Daniel P. Berrange + +diff -r 6981d4d832a9 Makefile +--- a/Makefile Mon Mar 02 11:13:33 2009 +0000 ++++ b/Makefile Mon Mar 02 11:13:38 2009 +0000 +@@ -148,6 +148,9 @@ OBJS+=vnc.o d3des.o + ifdef CONFIG_VNC_TLS + OBJS+=vnc-tls.o vnc-auth-vencrypt.o + endif ++ifdef CONFIG_VNC_SASL ++OBJS+=vnc-auth-sasl.o ++endif + + ifdef CONFIG_COCOA + OBJS+=cocoa.o +@@ -171,7 +174,7 @@ sdl.o: sdl.c keymaps.h sdl_keysym.h + + sdl.o audio/sdlaudio.o: CFLAGS += $(SDL_CFLAGS) + +-vnc.h: vnc-tls.h vnc-auth-vencrypt.h keymaps.h ++vnc.h: vnc-tls.h vnc-auth-vencrypt.h vnc-auth-sasl.h keymaps.h + + vnc.o: vnc.c vnc.h vnc_keysym.h vnchextile.h d3des.c d3des.h + +@@ -181,6 +184,8 @@ vnc-tls.o: vnc-tls.c vnc.h + + vnc-auth-vencrypt.o: vnc-auth-vencrypt.c vnc.h + ++vnc-auth-sasl.o: vnc-auth-sasl.c vnc.h ++ + curses.o: curses.c keymaps.h curses_keys.h + + bt-host.o: CFLAGS += $(CONFIG_BLUEZ_CFLAGS) +diff -r 6981d4d832a9 Makefile.target +--- a/Makefile.target Mon Mar 02 11:13:33 2009 +0000 ++++ b/Makefile.target Mon Mar 02 11:13:38 2009 +0000 +@@ -554,6 +554,11 @@ CPPFLAGS += $(CONFIG_VNC_TLS_CFLAGS) + LIBS += $(CONFIG_VNC_TLS_LIBS) + endif + ++ifdef CONFIG_VNC_SASL ++CPPFLAGS += $(CONFIG_VNC_SASL_CFLAGS) ++LIBS += $(CONFIG_VNC_SASL_LIBS) ++endif ++ + ifdef CONFIG_BLUEZ + LIBS += $(CONFIG_BLUEZ_LIBS) + endif +diff -r 6981d4d832a9 configure +--- a/configure Mon Mar 02 11:13:33 2009 +0000 ++++ b/configure Mon Mar 02 11:13:38 2009 +0000 +@@ -164,6 +164,7 @@ fmod_lib="" + fmod_inc="" + oss_lib="" + vnc_tls="yes" ++vnc_sasl="yes" + bsd="no" + linux="no" + solaris="no" +@@ -387,6 +388,8 @@ for opt do + ;; + --disable-vnc-tls) vnc_tls="no" + ;; ++ --disable-vnc-sasl) vnc_sasl="no" ++ ;; + --disable-slirp) slirp="no" + ;; + --disable-vde) vde="no" +@@ -544,6 +547,7 @@ echo " Availab + echo " --enable-mixemu enable mixer emulation" + echo " --disable-brlapi disable BrlAPI" + echo " --disable-vnc-tls disable TLS encryption for VNC server" ++echo " --disable-vnc-sasl disable SASL encryption for VNC server" + echo " --disable-curses disable curses output" + echo " --disable-bluez disable bluez stack connectivity" + echo " --disable-kvm disable KVM acceleration support" +@@ -823,6 +827,25 @@ EOF + fi + + ########################################## ++# VNC SASL detection ++if test "$vnc_sasl" = "yes" ; then ++cat > $TMPC < ++#include ++int main(void) { sasl_server_init(NULL, "qemu"); return 0; } ++EOF ++ # Assuming Cyrus-SASL installed in /usr prefix ++ vnc_sasl_cflags="" ++ vnc_sasl_libs="-lsasl2" ++ if $cc $ARCH_CFLAGS -o $TMPE ${OS_CFLAGS} $vnc_sasl_cflags $TMPC \ ++ $vnc_sasl_libs 2> /dev/null ; then ++ : ++ else ++ vnc_sasl="no" ++ fi ++fi ++ ++########################################## + # vde libraries probe + if test "$vde" = "yes" ; then + cat > $TMPC << EOF +@@ -1130,6 +1153,11 @@ if test "$vnc_tls" = "yes" ; then + echo " TLS CFLAGS $vnc_tls_cflags" + echo " TLS LIBS $vnc_tls_libs" + fi ++echo "VNC SASL support $vnc_sasl" ++if test "$vnc_sasl" = "yes" ; then ++ echo " SASL CFLAGS $vnc_sasl_cflags" ++ echo " SASL LIBS $vnc_sasl_libs" ++fi + if test -n "$sparc_cpu"; then + echo "Target Sparc Arch $sparc_cpu" + fi +@@ -1371,6 +1399,12 @@ if test "$vnc_tls" = "yes" ; then + echo "CONFIG_VNC_TLS_LIBS=$vnc_tls_libs" >> $config_mak + echo "#define CONFIG_VNC_TLS 1" >> $config_h + fi ++if test "$vnc_sasl" = "yes" ; then ++ echo "CONFIG_VNC_SASL=yes" >> $config_mak ++ echo "CONFIG_VNC_SASL_CFLAGS=$vnc_sasl_cflags" >> $config_mak ++ echo "CONFIG_VNC_SASL_LIBS=$vnc_sasl_libs" >> $config_mak ++ echo "#define CONFIG_VNC_SASL 1" >> $config_h ++fi + qemu_version=`head $source_path/VERSION` + echo "VERSION=$qemu_version" >>$config_mak + echo "#define QEMU_VERSION \"$qemu_version\"" >> $config_h +diff -r 6981d4d832a9 qemu-doc.texi +--- a/qemu-doc.texi Mon Mar 02 11:13:33 2009 +0000 ++++ b/qemu-doc.texi Mon Mar 02 11:13:38 2009 +0000 +@@ -616,6 +616,21 @@ path following this option specifies whe + be loaded from. See the @ref{vnc_security} section for details on generating + certificates. + ++@item sasl ++ ++Require that the client use SASL to authenticate with the VNC server. ++The exact choice of authentication method used is controlled from the ++system / user's SASL configuration file for the 'qemu' service. This ++is typically found in /etc/sasl2/qemu.conf. If running QEMU as an ++unprivileged user, an environment variable SASL_CONF_PATH can be used ++to make it search alternate locations for the service config. ++While some SASL auth methods can also provide data encryption (eg GSSAPI), ++it is recommended that SASL always be combined with the 'tls' and ++'x509' settings to enable use of SSL and server certificates. This ++ensures a data encryption preventing compromise of authentication ++credentials. See the @ref{vnc_security} section for details on using ++SASL authentication. ++ + @end table + + @end table +@@ -2061,7 +2076,10 @@ considerations depending on the deployme + * vnc_sec_certificate:: + * vnc_sec_certificate_verify:: + * vnc_sec_certificate_pw:: ++* vnc_sec_sasl:: ++* vnc_sec_certificate_sasl:: + * vnc_generate_cert:: ++* vnc_setup_sasl:: + @end menu + @node vnc_sec_none + @subsection Without passwords +@@ -2144,6 +2162,41 @@ Password: ******** + (qemu) + @end example + ++ ++@node vnc_sec_sasl ++@subsection With SASL authentication ++ ++The SASL authentication method is a VNC extension, that provides an ++easily extendable, pluggable authentication method. This allows for ++integration with a wide range of authentication mechanisms, such as ++PAM, GSSAPI/Kerberos, LDAP, SQL databases, one-time keys and more. ++The strength of the authentication depends on the exact mechanism ++configured. If the chosen mechanism also provides a SSF layer, then ++it will encrypt the datastream as well. ++ ++Refer to the later docs on how to choose the exact SASL mechanism ++used for authentication, but assuming use of one supporting SSF, ++then QEMU can be launched with: ++ ++@example ++qemu [...OPTIONS...] -vnc :1,sasl -monitor stdio ++@end example ++ ++@node vnc_sec_certificate_sasl ++@subsection With x509 certificates and SASL authentication ++ ++If the desired SASL authentication mechanism does not supported ++SSF layers, then it is strongly advised to run it in combination ++with TLS and x509 certificates. This provides securely encrypted ++data stream, avoiding risk of compromising of the security ++credentials. This can be enabled, by combining the 'sasl' option ++with the aforementioned TLS + x509 options: ++ ++@example ++qemu [...OPTIONS...] -vnc :1,tls,x509,sasl -monitor stdio ++@end example ++ ++ + @node vnc_generate_cert + @subsection Generating certificates for VNC + +@@ -2255,6 +2308,50 @@ EOF + The @code{client-key.pem} and @code{client-cert.pem} files should now be securely + copied to the client for which they were generated. + ++ ++@node vnc_setup_sasl ++ ++@subsection Configuring SASL mechanisms ++ ++The following documentation assumes use of the Cyrus SASL implementation on a ++Linux host, but the principals should apply to any other SASL impl. When SASL ++is enabled, the mechanism configuration will be loaded from system default ++SASL service config /etc/sasl2/qemu.conf. If running QEMU as an ++unprivileged user, an environment variable SASL_CONF_PATH can be used ++to make it search alternate locations for the service config. ++ ++The default configuration might contain ++ ++@example ++mech_list: digest-md5 ++sasldb_path: /etc/qemu/passwd.db ++@end example ++ ++This says to use the 'Digest MD5' mechanism, which is similar to the HTTP ++Digest-MD5 mechanism. The list of valid usernames & passwords is maintained ++in the /etc/qemu/passwd.db file, and can be updated using the saslpasswd2 ++command. While this mechanism is easy to configure and use, it is not ++considered secure by modern standards, so only suitable for developers / ++ad-hoc testing. ++ ++A more serious deployment might use Kerberos, which is done with the 'gssapi' ++mechanism ++ ++@example ++mech_list: gssapi ++keytab: /etc/qemu/krb5.tab ++@end example ++ ++For this to work the administrator of your KDC must generate a Kerberos ++principal for the server, with a name of 'qemu/somehost.example.com@@EXAMPLE.COM' ++replacing 'somehost.example.com' with the fully qualified host name of the ++machine running QEMU, and 'EXAMPLE.COM' with the Keberos Realm. ++ ++Other configurations will be left as an exercise for the reader. It should ++be noted that only Digest-MD5 and GSSAPI provides a SSF layer for data ++encryption. For all other mechanisms, VNC should always be configured to ++use TLS and x509 certificates to protect security credentials from snooping. ++ + @node gdb_usage + @section GDB usage + +diff -r 6981d4d832a9 qemu.sasl +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/qemu.sasl Mon Mar 02 11:13:38 2009 +0000 +@@ -0,0 +1,34 @@ ++# If you want to use the non-TLS socket, then you *must* include ++# the GSSAPI or DIGEST-MD5 mechanisms, because they are the only ++# ones that can offer session encryption as well as authentication. ++# ++# If you're only using TLS, then you can turn on any mechanisms ++# you like for authentication, because TLS provides the encryption ++# ++# Default to a simple username+password mechanism ++# NB digest-md5 is no longer considered secure by current standards ++mech_list: digest-md5 ++ ++# Before you can use GSSAPI, you need a service principle on the ++# KDC server for libvirt, and that to be exported to the keytab ++# file listed below ++#mech_list: gssapi ++# ++# You can also list many mechanisms at once, then the user can choose ++# by adding '?auth=sasl.gssapi' to their libvirt URI, eg ++# qemu+tcp://hostname/system?auth=sasl.gssapi ++#mech_list: digest-md5 gssapi ++ ++# Some older builds of MIT kerberos on Linux ignore this option & ++# instead need KRB5_KTNAME env var. ++# For modern Linux, and other OS, this should be sufficient ++keytab: /etc/qemu/krb5.tab ++ ++# If using digest-md5 for username/passwds, then this is the file ++# containing the passwds. Use 'saslpasswd2 -a qemu [username]' ++# to add entries, and 'sasldblistusers2 -a qemu' to browse it ++sasldb_path: /etc/qemu/passwd.db ++ ++ ++auxprop_plugin: sasldb ++ +diff -r 6981d4d832a9 vnc-auth-sasl.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/vnc-auth-sasl.c Mon Mar 02 11:13:38 2009 +0000 +@@ -0,0 +1,626 @@ ++/* ++ * QEMU VNC display driver: SASL auth protocol ++ * ++ * Copyright (C) 2009 Red Hat, Inc ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++#include "vnc.h" ++ ++/* Max amount of data we send/recv for SASL steps to prevent DOS */ ++#define SASL_DATA_MAX_LEN (1024 * 1024) ++ ++ ++void vnc_sasl_client_cleanup(VncState *vs) ++{ ++ if (vs->sasl.conn) { ++ vs->sasl.runSSF = vs->sasl.waitWriteSSF = vs->sasl.wantSSF = 0; ++ vs->sasl.encodedLength = vs->sasl.encodedOffset = 0; ++ vs->sasl.encoded = NULL; ++ free(vs->sasl.username); ++ free(vs->sasl.mechlist); ++ vs->sasl.username = vs->sasl.mechlist = NULL; ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ } ++} ++ ++ ++long vnc_client_write_sasl(VncState *vs) ++{ ++ long ret; ++ ++ VNC_DEBUG("Write SASL: Pending output %p size %d offset %d Encoded: %p size %d offset %d\n", ++ vs->output.buffer, vs->output.capacity, vs->output.offset, ++ vs->sasl.encoded, vs->sasl.encodedLength, vs->sasl.encodedOffset); ++ ++ if (!vs->sasl.encoded) { ++ int err; ++ err = sasl_encode(vs->sasl.conn, ++ (char *)vs->output.buffer, ++ vs->output.offset, ++ (const char **)&vs->sasl.encoded, ++ &vs->sasl.encodedLength); ++ if (err != SASL_OK) ++ return vnc_client_io_error(vs, -1, EIO); ++ ++ vs->sasl.encodedOffset = 0; ++ } ++ ++ ret = vnc_client_write_buf(vs, ++ vs->sasl.encoded + vs->sasl.encodedOffset, ++ vs->sasl.encodedLength - vs->sasl.encodedOffset); ++ if (!ret) ++ return 0; ++ ++ vs->sasl.encodedOffset += ret; ++ if (vs->sasl.encodedOffset == vs->sasl.encodedLength) { ++ vs->output.offset = 0; ++ vs->sasl.encoded = NULL; ++ vs->sasl.encodedOffset = vs->sasl.encodedLength = 0; ++ } ++ ++ /* Can't merge this block with one above, because ++ * someone might have written more unencrypted ++ * data in vs->output while we were processing ++ * SASL encoded output ++ */ ++ if (vs->output.offset == 0) { ++ qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); ++ } ++ ++ return ret; ++} ++ ++ ++long vnc_client_read_sasl(VncState *vs) ++{ ++ long ret; ++ uint8_t encoded[4096]; ++ const char *decoded; ++ unsigned int decodedLen; ++ int err; ++ ++ ret = vnc_client_read_buf(vs, encoded, sizeof(encoded)); ++ if (!ret) ++ return 0; ++ ++ err = sasl_decode(vs->sasl.conn, ++ (char *)encoded, ret, ++ &decoded, &decodedLen); ++ ++ if (err != SASL_OK) ++ return vnc_client_io_error(vs, -1, -EIO); ++ VNC_DEBUG("Read SASL Encoded %p size %ld Decoded %p size %d\n", ++ encoded, ret, decoded, decodedLen); ++ buffer_reserve(&vs->input, decodedLen); ++ buffer_append(&vs->input, decoded, decodedLen); ++ return decodedLen; ++} ++ ++ ++static int vnc_auth_sasl_check_access(VncState *vs) ++{ ++ const void *val; ++ int err; ++ ++ err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val); ++ if (err != SASL_OK) { ++ VNC_DEBUG("cannot query SASL username on connection %d (%s)\n", ++ err, sasl_errstring(err, NULL, NULL)); ++ return -1; ++ } ++ if (val == NULL) { ++ VNC_DEBUG("no client username was found\n"); ++ return -1; ++ } ++ VNC_DEBUG("SASL client username %s\n", (const char *)val); ++ ++ vs->sasl.username = qemu_strdup((const char*)val); ++ ++ return 0; ++} ++ ++static int vnc_auth_sasl_check_ssf(VncState *vs) ++{ ++ const void *val; ++ int err, ssf; ++ ++ if (!vs->sasl.wantSSF) ++ return 1; ++ ++ err = sasl_getprop(vs->sasl.conn, SASL_SSF, &val); ++ if (err != SASL_OK) ++ return 0; ++ ++ ssf = *(const int *)val; ++ VNC_DEBUG("negotiated an SSF of %d\n", ssf); ++ if (ssf < 56) ++ return 0; /* 56 is good for Kerberos */ ++ ++ /* Only setup for read initially, because we're about to send an RPC ++ * reply which must be in plain text. When the next incoming RPC ++ * arrives, we'll switch on writes too ++ * ++ * cf qemudClientReadSASL in qemud.c ++ */ ++ vs->sasl.runSSF = 1; ++ ++ /* We have a SSF that's good enough */ ++ return 1; ++} ++ ++/* ++ * Step Msg ++ * ++ * Input from client: ++ * ++ * u32 clientin-length ++ * u8-array clientin-string ++ * ++ * Output to client: ++ * ++ * u32 serverout-length ++ * u8-array serverout-strin ++ * u8 continue ++ */ ++ ++static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len); ++ ++static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t len) ++{ ++ uint32_t datalen = len; ++ const char *serverout; ++ unsigned int serveroutlen; ++ int err; ++ char *clientdata = NULL; ++ ++ /* NB, distinction of NULL vs "" is *critical* in SASL */ ++ if (datalen) { ++ clientdata = (char*)data; ++ clientdata[datalen-1] = '\0'; /* Wire includes '\0', but make sure */ ++ datalen--; /* Don't count NULL byte when passing to _start() */ ++ } ++ ++ VNC_DEBUG("Step using SASL Data %p (%d bytes)\n", ++ clientdata, datalen); ++ err = sasl_server_step(vs->sasl.conn, ++ clientdata, ++ datalen, ++ &serverout, ++ &serveroutlen); ++ if (err != SASL_OK && ++ err != SASL_CONTINUE) { ++ VNC_DEBUG("sasl step failed %d (%s)\n", ++ err, sasl_errdetail(vs->sasl.conn)); ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ ++ if (serveroutlen > SASL_DATA_MAX_LEN) { ++ VNC_DEBUG("sasl step reply data too long %d\n", ++ serveroutlen); ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ ++ VNC_DEBUG("SASL return data %d bytes, nil; %d\n", ++ serveroutlen, serverout ? 0 : 1); ++ ++ if (serveroutlen) { ++ vnc_write_u32(vs, serveroutlen + 1); ++ vnc_write(vs, serverout, serveroutlen + 1); ++ } else { ++ vnc_write_u32(vs, 0); ++ } ++ ++ /* Whether auth is complete */ ++ vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1); ++ ++ if (err == SASL_CONTINUE) { ++ VNC_DEBUG("%s", "Authentication must continue\n"); ++ /* Wait for step length */ ++ vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4); ++ } else { ++ if (!vnc_auth_sasl_check_ssf(vs)) { ++ VNC_DEBUG("Authentication rejected for weak SSF %d\n", vs->csock); ++ goto authreject; ++ } ++ ++ /* Check username whitelist ACL */ ++ if (vnc_auth_sasl_check_access(vs) < 0) { ++ VNC_DEBUG("Authentication rejected for ACL %d\n", vs->csock); ++ goto authreject; ++ } ++ ++ VNC_DEBUG("Authentication successful %d\n", vs->csock); ++ vnc_write_u32(vs, 0); /* Accept auth */ ++ /* ++ * Delay writing in SSF encoded mode until pending output ++ * buffer is written ++ */ ++ if (vs->sasl.runSSF) ++ vs->sasl.waitWriteSSF = vs->output.offset; ++ start_client_init(vs); ++ } ++ ++ return 0; ++ ++ authreject: ++ vnc_write_u32(vs, 1); /* Reject auth */ ++ vnc_write_u32(vs, sizeof("Authentication failed")); ++ vnc_write(vs, "Authentication failed", sizeof("Authentication failed")); ++ vnc_flush(vs); ++ vnc_client_error(vs); ++ return -1; ++ ++ authabort: ++ vnc_client_error(vs); ++ return -1; ++} ++ ++static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len) ++{ ++ uint32_t steplen = read_u32(data, 0); ++ VNC_DEBUG("Got client step len %d\n", steplen); ++ if (steplen > SASL_DATA_MAX_LEN) { ++ VNC_DEBUG("Too much SASL data %d\n", steplen); ++ vnc_client_error(vs); ++ return -1; ++ } ++ ++ if (steplen == 0) ++ return protocol_client_auth_sasl_step(vs, NULL, 0); ++ else ++ vnc_read_when(vs, protocol_client_auth_sasl_step, steplen); ++ return 0; ++} ++ ++/* ++ * Start Msg ++ * ++ * Input from client: ++ * ++ * u32 clientin-length ++ * u8-array clientin-string ++ * ++ * Output to client: ++ * ++ * u32 serverout-length ++ * u8-array serverout-strin ++ * u8 continue ++ */ ++ ++#define SASL_DATA_MAX_LEN (1024 * 1024) ++ ++static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t len) ++{ ++ uint32_t datalen = len; ++ const char *serverout; ++ unsigned int serveroutlen; ++ int err; ++ char *clientdata = NULL; ++ ++ /* NB, distinction of NULL vs "" is *critical* in SASL */ ++ if (datalen) { ++ clientdata = (char*)data; ++ clientdata[datalen-1] = '\0'; /* Should be on wire, but make sure */ ++ datalen--; /* Don't count NULL byte when passing to _start() */ ++ } ++ ++ VNC_DEBUG("Start SASL auth with mechanism %s. Data %p (%d bytes)\n", ++ vs->sasl.mechlist, clientdata, datalen); ++ err = sasl_server_start(vs->sasl.conn, ++ vs->sasl.mechlist, ++ clientdata, ++ datalen, ++ &serverout, ++ &serveroutlen); ++ if (err != SASL_OK && ++ err != SASL_CONTINUE) { ++ VNC_DEBUG("sasl start failed %d (%s)\n", ++ err, sasl_errdetail(vs->sasl.conn)); ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ if (serveroutlen > SASL_DATA_MAX_LEN) { ++ VNC_DEBUG("sasl start reply data too long %d\n", ++ serveroutlen); ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ ++ VNC_DEBUG("SASL return data %d bytes, nil; %d\n", ++ serveroutlen, serverout ? 0 : 1); ++ ++ if (serveroutlen) { ++ vnc_write_u32(vs, serveroutlen + 1); ++ vnc_write(vs, serverout, serveroutlen + 1); ++ } else { ++ vnc_write_u32(vs, 0); ++ } ++ ++ /* Whether auth is complete */ ++ vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1); ++ ++ if (err == SASL_CONTINUE) { ++ VNC_DEBUG("%s", "Authentication must continue\n"); ++ /* Wait for step length */ ++ vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4); ++ } else { ++ if (!vnc_auth_sasl_check_ssf(vs)) { ++ VNC_DEBUG("Authentication rejected for weak SSF %d\n", vs->csock); ++ goto authreject; ++ } ++ ++ /* Check username whitelist ACL */ ++ if (vnc_auth_sasl_check_access(vs) < 0) { ++ VNC_DEBUG("Authentication rejected for ACL %d\n", vs->csock); ++ goto authreject; ++ } ++ ++ VNC_DEBUG("Authentication successful %d\n", vs->csock); ++ vnc_write_u32(vs, 0); /* Accept auth */ ++ start_client_init(vs); ++ } ++ ++ return 0; ++ ++ authreject: ++ vnc_write_u32(vs, 1); /* Reject auth */ ++ vnc_write_u32(vs, sizeof("Authentication failed")); ++ vnc_write(vs, "Authentication failed", sizeof("Authentication failed")); ++ vnc_flush(vs); ++ vnc_client_error(vs); ++ return -1; ++ ++ authabort: ++ vnc_client_error(vs); ++ return -1; ++} ++ ++static int protocol_client_auth_sasl_start_len(VncState *vs, uint8_t *data, size_t len) ++{ ++ uint32_t startlen = read_u32(data, 0); ++ VNC_DEBUG("Got client start len %d\n", startlen); ++ if (startlen > SASL_DATA_MAX_LEN) { ++ VNC_DEBUG("Too much SASL data %d\n", startlen); ++ vnc_client_error(vs); ++ return -1; ++ } ++ ++ if (startlen == 0) ++ return protocol_client_auth_sasl_start(vs, NULL, 0); ++ ++ vnc_read_when(vs, protocol_client_auth_sasl_start, startlen); ++ return 0; ++} ++ ++static int protocol_client_auth_sasl_mechname(VncState *vs, uint8_t *data, size_t len) ++{ ++ char *mechname = malloc(len + 1); ++ if (!mechname) { ++ VNC_DEBUG("Out of memory reading mechname\n"); ++ vnc_client_error(vs); ++ } ++ strncpy(mechname, (char*)data, len); ++ mechname[len] = '\0'; ++ VNC_DEBUG("Got client mechname '%s' check against '%s'\n", ++ mechname, vs->sasl.mechlist); ++ ++ if (strncmp(vs->sasl.mechlist, mechname, len) == 0) { ++ if (vs->sasl.mechlist[len] != '\0' && ++ vs->sasl.mechlist[len] != ',') { ++ VNC_DEBUG("One %d", vs->sasl.mechlist[len]); ++ vnc_client_error(vs); ++ return -1; ++ } ++ } else { ++ char *offset = strstr(vs->sasl.mechlist, mechname); ++ VNC_DEBUG("Two %p\n", offset); ++ if (!offset) { ++ vnc_client_error(vs); ++ return -1; ++ } ++ VNC_DEBUG("Two '%s'\n", offset); ++ if (offset[-1] != ',' || ++ (offset[len] != '\0'&& ++ offset[len] != ',')) { ++ vnc_client_error(vs); ++ return -1; ++ } ++ } ++ ++ free(vs->sasl.mechlist); ++ vs->sasl.mechlist = mechname; ++ ++ VNC_DEBUG("Validated mechname '%s'\n", mechname); ++ vnc_read_when(vs, protocol_client_auth_sasl_start_len, 4); ++ return 0; ++} ++ ++static int protocol_client_auth_sasl_mechname_len(VncState *vs, uint8_t *data, size_t len) ++{ ++ uint32_t mechlen = read_u32(data, 0); ++ VNC_DEBUG("Got client mechname len %d\n", mechlen); ++ if (mechlen > 100) { ++ VNC_DEBUG("Too long SASL mechname data %d\n", mechlen); ++ vnc_client_error(vs); ++ return -1; ++ } ++ if (mechlen < 1) { ++ VNC_DEBUG("Too short SASL mechname %d\n", mechlen); ++ vnc_client_error(vs); ++ return -1; ++ } ++ vnc_read_when(vs, protocol_client_auth_sasl_mechname,mechlen); ++ return 0; ++} ++ ++#define USES_X509_AUTH(vs) \ ++ ((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ ++ (vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ ++ (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN || \ ++ (vs)->subauth == VNC_AUTH_VENCRYPT_X509SASL) ++ ++ ++void start_auth_sasl(VncState *vs) ++{ ++ const char *mechlist = NULL; ++ sasl_security_properties_t secprops; ++ int err; ++ char *localAddr, *remoteAddr; ++ int mechlistlen; ++ ++ VNC_DEBUG("Initialize SASL auth %d\n", vs->csock); ++ ++ /* Get local & remote client addresses in form IPADDR;PORT */ ++ if (!(localAddr = vnc_socket_local_addr("%s;%s", vs->csock))) ++ goto authabort; ++ ++ if (!(remoteAddr = vnc_socket_remote_addr("%s;%s", vs->csock))) { ++ free(localAddr); ++ goto authabort; ++ } ++ ++ err = sasl_server_new("vnc", ++ NULL, /* FQDN - just delegates to gethostname */ ++ NULL, /* User realm */ ++ localAddr, ++ remoteAddr, ++ NULL, /* Callbacks, not needed */ ++ SASL_SUCCESS_DATA, ++ &vs->sasl.conn); ++ free(localAddr); ++ free(remoteAddr); ++ localAddr = remoteAddr = NULL; ++ ++ if (err != SASL_OK) { ++ VNC_DEBUG("sasl context setup failed %d (%s)", ++ err, sasl_errstring(err, NULL, NULL)); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ ++#ifdef CONFIG_VNC_TLS ++ /* Inform SASL that we've got an external SSF layer from TLS/x509 */ ++ if (vs->vd->auth == VNC_AUTH_VENCRYPT && ++ vs->vd->subauth == VNC_AUTH_VENCRYPT_X509SASL) { ++ gnutls_cipher_algorithm_t cipher; ++ sasl_ssf_t ssf; ++ ++ cipher = gnutls_cipher_get(vs->tls.session); ++ if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) { ++ VNC_DEBUG("%s", "cannot TLS get cipher size\n"); ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ ssf *= 8; /* tls key size is bytes, sasl wants bits */ ++ ++ err = sasl_setprop(vs->sasl.conn, SASL_SSF_EXTERNAL, &ssf); ++ if (err != SASL_OK) { ++ VNC_DEBUG("cannot set SASL external SSF %d (%s)\n", ++ err, sasl_errstring(err, NULL, NULL)); ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ } else ++#endif /* CONFIG_VNC_TLS */ ++ vs->sasl.wantSSF = 1; ++ ++ memset (&secprops, 0, sizeof secprops); ++ /* Inform SASL that we've got an external SSF layer from TLS */ ++ if (strncmp(vs->vd->display, "unix:", 5) == 0 ++#ifdef CONFIG_VNC_TLS ++ /* Disable SSF, if using TLS+x509+SASL only. TLS without x509 ++ is not sufficiently strong */ ++ || (vs->vd->auth == VNC_AUTH_VENCRYPT && ++ vs->vd->subauth == VNC_AUTH_VENCRYPT_X509SASL) ++#endif /* CONFIG_VNC_TLS */ ++ ) { ++ /* If we've got TLS or UNIX domain sock, we don't care about SSF */ ++ secprops.min_ssf = 0; ++ secprops.max_ssf = 0; ++ secprops.maxbufsize = 8192; ++ secprops.security_flags = 0; ++ } else { ++ /* Plain TCP, better get an SSF layer */ ++ secprops.min_ssf = 56; /* Good enough to require kerberos */ ++ secprops.max_ssf = 100000; /* Arbitrary big number */ ++ secprops.maxbufsize = 8192; ++ /* Forbid any anonymous or trivially crackable auth */ ++ secprops.security_flags = ++ SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT; ++ } ++ ++ err = sasl_setprop(vs->sasl.conn, SASL_SEC_PROPS, &secprops); ++ if (err != SASL_OK) { ++ VNC_DEBUG("cannot set SASL security props %d (%s)\n", ++ err, sasl_errstring(err, NULL, NULL)); ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ ++ err = sasl_listmech(vs->sasl.conn, ++ NULL, /* Don't need to set user */ ++ "", /* Prefix */ ++ ",", /* Separator */ ++ "", /* Suffix */ ++ &mechlist, ++ NULL, ++ NULL); ++ if (err != SASL_OK) { ++ VNC_DEBUG("cannot list SASL mechanisms %d (%s)\n", ++ err, sasl_errdetail(vs->sasl.conn)); ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ VNC_DEBUG("Available mechanisms for client: '%s'\n", mechlist); ++ ++ if (!(vs->sasl.mechlist = strdup(mechlist))) { ++ VNC_DEBUG("Out of memory"); ++ sasl_dispose(&vs->sasl.conn); ++ vs->sasl.conn = NULL; ++ goto authabort; ++ } ++ mechlistlen = strlen(mechlist); ++ vnc_write_u32(vs, mechlistlen); ++ vnc_write(vs, mechlist, mechlistlen); ++ vnc_flush(vs); ++ ++ VNC_DEBUG("Wait for client mechname length\n"); ++ vnc_read_when(vs, protocol_client_auth_sasl_mechname_len, 4); ++ ++ return; ++ ++ authabort: ++ vnc_client_error(vs); ++ return; ++} ++ ++ +diff -r 6981d4d832a9 vnc-auth-sasl.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/vnc-auth-sasl.h Mon Mar 02 11:13:38 2009 +0000 +@@ -0,0 +1,67 @@ ++/* ++ * QEMU VNC display driver: SASL auth protocol ++ * ++ * Copyright (C) 2009 Red Hat, Inc ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++ ++#ifndef __QEMU_VNC_AUTH_SASL_H__ ++#define __QEMU_VNC_AUTH_SASL_H__ ++ ++ ++#include ++ ++typedef struct VncStateSASL VncStateSASL; ++ ++struct VncStateSASL { ++ sasl_conn_t *conn; ++ /* If we want to negotiate an SSF layer with client */ ++ int wantSSF :1; ++ /* If we are now running the SSF layer */ ++ int runSSF :1; ++ /* ++ * If this is non-zero, then wait for that many bytes ++ * to be written plain, before switching to SSF encoding ++ * This allows the VNC auth result to finish being ++ * written in plain. ++ */ ++ unsigned int waitWriteSSF; ++ ++ /* ++ * Buffering encoded data to allow more clear data ++ * to be stuffed onto the output buffer ++ */ ++ const uint8_t *encoded; ++ unsigned int encodedLength; ++ unsigned int encodedOffset; ++ char *username; ++ char *mechlist; ++}; ++ ++void vnc_sasl_client_cleanup(VncState *vs); ++ ++long vnc_client_read_sasl(VncState *vs); ++long vnc_client_write_sasl(VncState *vs); ++ ++void start_auth_sasl(VncState *vs); ++ ++#endif /* __QEMU_VNC_AUTH_SASL_H__ */ ++ +diff -r 6981d4d832a9 vnc-auth-vencrypt.c +--- a/vnc-auth-vencrypt.c Mon Mar 02 11:13:33 2009 +0000 ++++ b/vnc-auth-vencrypt.c Mon Mar 02 11:13:38 2009 +0000 +@@ -43,8 +43,15 @@ static void start_auth_vencrypt_subauth( + start_auth_vnc(vs); + break; + ++#ifdef CONFIG_VNC_SASL ++ case VNC_AUTH_VENCRYPT_TLSSASL: ++ case VNC_AUTH_VENCRYPT_X509SASL: ++ VNC_DEBUG("Start TLS auth SASL\n"); ++ return start_auth_sasl(vs); ++#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 subauth %d server bug\n", vs->vd->auth); + vnc_write_u8(vs, 1); + if (vs->minor >= 8) { + static const char err[] = "Unsupported authentication type"; +@@ -105,7 +112,8 @@ static void vnc_tls_handshake_io(void *o + #define NEED_X509_AUTH(vs) \ + ((vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ + (vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ +- (vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509PLAIN) ++ (vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509PLAIN || \ ++ (vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509SASL) + + + static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len) +diff -r 6981d4d832a9 vnc.c +--- a/vnc.c Mon Mar 02 11:13:33 2009 +0000 ++++ b/vnc.c Mon Mar 02 11:13:38 2009 +0000 +@@ -68,7 +68,8 @@ static char *addr_to_string(const char * + return addr; + } + +-static char *vnc_socket_local_addr(const char *format, int fd) { ++ ++char *vnc_socket_local_addr(const char *format, int fd) { + struct sockaddr_storage sa; + socklen_t salen; + +@@ -79,7 +80,8 @@ static char *vnc_socket_local_addr(const + return addr_to_string(format, &sa, salen); + } + +-static char *vnc_socket_remote_addr(const char *format, int fd) { ++ ++char *vnc_socket_remote_addr(const char *format, int fd) { + struct sockaddr_storage sa; + socklen_t salen; + +@@ -125,12 +127,18 @@ static const char *vnc_auth_name(VncDisp + 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"; + default: + return "vencrypt"; + } + #else + return "vencrypt"; + #endif ++ case VNC_AUTH_SASL: ++ return "sasl"; + } + return "unknown"; + } +@@ -278,7 +286,7 @@ static void vnc_framebuffer_update(VncSt + vnc_write_s32(vs, encoding); + } + +-static void buffer_reserve(Buffer *buffer, size_t len) ++void buffer_reserve(Buffer *buffer, size_t len) + { + if ((buffer->capacity - buffer->offset) < len) { + buffer->capacity += (len + 1024); +@@ -290,22 +298,22 @@ static void buffer_reserve(Buffer *buffe + } + } + +-static int buffer_empty(Buffer *buffer) ++int buffer_empty(Buffer *buffer) + { + return buffer->offset == 0; + } + +-static uint8_t *buffer_end(Buffer *buffer) ++uint8_t *buffer_end(Buffer *buffer) + { + return buffer->buffer + buffer->offset; + } + +-static void buffer_reset(Buffer *buffer) ++void buffer_reset(Buffer *buffer) + { + buffer->offset = 0; + } + +-static void buffer_append(Buffer *buffer, const void *data, size_t len) ++void buffer_append(Buffer *buffer, const void *data, size_t len) + { + memcpy(buffer->buffer + buffer->offset, data, len); + buffer->offset += len; +@@ -821,7 +829,8 @@ static void audio_del(VncState *vs) + } + } + +-static int vnc_client_io_error(VncState *vs, int ret, int last_errno) ++ ++int vnc_client_io_error(VncState *vs, int ret, int last_errno) + { + if (ret == 0 || ret == -1) { + if (ret == -1) { +@@ -847,6 +856,9 @@ static int vnc_client_io_error(VncState + #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; +@@ -877,14 +889,28 @@ void vnc_client_error(VncState *vs) + vnc_client_io_error(vs, -1, EINVAL); + } + +-void vnc_client_write(void *opaque) ++ ++/* ++ * Called to write a chunk of data to the client socket. The data may ++ * be the raw data, or may have already been encoded by SASL. ++ * The data will be written either straight onto the socket, or ++ * written via the GNUTLS wrappers, if TLS/SSL encryption is enabled ++ * ++ * NB, it is theoretically possible to have 2 layers of encryption, ++ * both SASL, and this TLS layer. It is highly unlikely in practice ++ * though, since SASL encryption will typically be a no-op if TLS ++ * is active ++ * ++ * Returns the number of bytes written, which may be less than ++ * the requested 'datalen' if the socket would block. Returns ++ * -1 on error, and disconnects the client socket. ++ */ ++long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) + { + long ret; +- VncState *vs = opaque; +- + #ifdef CONFIG_VNC_TLS + if (vs->tls.session) { +- ret = gnutls_write(vs->tls.session, vs->output.buffer, vs->output.offset); ++ ret = gnutls_write(vs->tls.session, data, datalen); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; +@@ -894,10 +920,42 @@ void vnc_client_write(void *opaque) + } + } 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()); ++ ret = send(vs->csock, data, datalen, 0); ++ VNC_DEBUG("Wrote wire %p %d -> %ld\n", data, datalen, ret); ++ return vnc_client_io_error(vs, ret, socket_error()); ++} ++ ++ ++/* ++ * Called to write buffered data to the client socket, when not ++ * using any SASL SSF encryption layers. Will write as much data ++ * as possible without blocking. If all buffered data is written, ++ * will switch the FD poll() handler back to read monitoring. ++ * ++ * Returns the number of bytes written, which may be less than ++ * the buffered output data if the socket would block. Returns ++ * -1 on error, and disconnects the client socket. ++ */ ++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", ++ vs->output.buffer, vs->output.capacity, vs->output.offset, ++ vs->sasl.waitWriteSSF); ++ ++ if (vs->sasl.conn && ++ vs->sasl.runSSF && ++ vs->sasl.waitWriteSSF) { ++ ret = vnc_client_write_buf(vs, vs->output.buffer, vs->sasl.waitWriteSSF); ++ if (ret) ++ vs->sasl.waitWriteSSF -= ret; ++ } else ++#endif /* CONFIG_VNC_SASL */ ++ ret = vnc_client_write_buf(vs, vs->output.buffer, vs->output.offset); + if (!ret) +- return; ++ return 0; + + memmove(vs->output.buffer, vs->output.buffer + ret, (vs->output.offset - ret)); + vs->output.offset -= ret; +@@ -905,6 +963,29 @@ void vnc_client_write(void *opaque) + if (vs->output.offset == 0) { + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); + } ++ ++ return ret; ++} ++ ++ ++/* ++ * First function called whenever there is data to be written to ++ * the client socket. Will delegate actual work according to whether ++ * SASL SSF layers are enabled (thus requiring encryption calls) ++ */ ++void vnc_client_write(void *opaque) ++{ ++ long ret; ++ VncState *vs = opaque; ++ ++#ifdef CONFIG_VNC_SASL ++ if (vs->sasl.conn && ++ vs->sasl.runSSF && ++ !vs->sasl.waitWriteSSF) ++ ret = vnc_client_write_sasl(vs); ++ else ++#endif /* CONFIG_VNC_SASL */ ++ ret = vnc_client_write_plain(vs); + } + + void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) +@@ -913,16 +994,28 @@ void vnc_read_when(VncState *vs, VncRead + vs->read_handler_expect = expecting; + } + +-void vnc_client_read(void *opaque) ++ ++/* ++ * Called to read a chunk of data from the client socket. The data may ++ * be the raw data, or may need to be further decoded by SASL. ++ * The data will be read either straight from to the socket, or ++ * read via the GNUTLS wrappers, if TLS/SSL encryption is enabled ++ * ++ * NB, it is theoretically possible to have 2 layers of encryption, ++ * both SASL, and this TLS layer. It is highly unlikely in practice ++ * though, since SASL encryption will typically be a no-op if TLS ++ * is active ++ * ++ * Returns the number of bytes read, which may be less than ++ * the requested 'datalen' if the socket would block. Returns ++ * -1 on error, and disconnects the client socket. ++ */ ++long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) + { +- VncState *vs = opaque; + long ret; +- +- buffer_reserve(&vs->input, 4096); +- + #ifdef CONFIG_VNC_TLS + if (vs->tls.session) { +- ret = gnutls_read(vs->tls.session, buffer_end(&vs->input), 4096); ++ ret = gnutls_read(vs->tls.session, data, datalen); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; +@@ -932,13 +1025,53 @@ void vnc_client_read(void *opaque) + } + } else + #endif /* CONFIG_VNC_TLS */ +- ret = recv(vs->csock, buffer_end(&vs->input), 4096, 0); +- ret = vnc_client_io_error(vs, ret, socket_error()); ++ ret = recv(vs->csock, data, datalen, 0); ++ VNC_DEBUG("Read wire %p %d -> %ld\n", data, datalen, ret); ++ return vnc_client_io_error(vs, ret, socket_error()); ++} ++ ++ ++/* ++ * Called to read data from the client socket to the input buffer, ++ * when not using any SASL SSF encryption layers. Will read as much ++ * data as possible without blocking. ++ * ++ * Returns the number of bytes read. Returns -1 on error, and ++ * disconnects the client socket. ++ */ ++static long vnc_client_read_plain(VncState *vs) ++{ ++ int ret; ++ VNC_DEBUG("Read plain %p size %d offset %d\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); ++ if (!ret) ++ return 0; ++ vs->input.offset += ret; ++ return ret; ++} ++ ++ ++/* ++ * First function called whenever there is more data to be read from ++ * the client socket. Will delegate actual work according to whether ++ * SASL SSF layers are enabled (thus requiring decryption calls) ++ */ ++void vnc_client_read(void *opaque) ++{ ++ VncState *vs = opaque; ++ long ret; ++ ++#ifdef CONFIG_VNC_SASL ++ if (vs->sasl.conn && vs->sasl.runSSF) ++ ret = vnc_client_read_sasl(vs); ++ else ++#endif /* CONFIG_VNC_SASL */ ++ ret = vnc_client_read_plain(vs); + if (!ret) + return; + +- vs->input.offset += ret; +- + while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) { + size_t len = vs->read_handler_expect; + int ret; +@@ -1722,6 +1855,13 @@ static int protocol_client_auth(VncState + break; + #endif /* CONFIG_VNC_TLS */ + ++#ifdef CONFIG_VNC_SASL ++ case VNC_AUTH_SASL: ++ VNC_DEBUG("Accept SASL auth\n"); ++ start_auth_sasl(vs); ++ break; ++#endif /* CONFIG_VNC_SASL */ ++ + default: /* Should not be possible, but just in case */ + VNC_DEBUG("Reject auth %d\n", vs->vd->auth); + vnc_write_u8(vs, 1); +@@ -1923,6 +2063,10 @@ int vnc_display_open(DisplayState *ds, c + #ifdef CONFIG_VNC_TLS + int tls = 0, x509 = 0; + #endif ++#ifdef CONFIG_VNC_SASL ++ int sasl = 0; ++ int saslErr; ++#endif + + if (!vnc_display) + return -1; +@@ -1942,6 +2086,10 @@ int vnc_display_open(DisplayState *ds, c + 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 */ ++#endif + #ifdef CONFIG_VNC_TLS + } else if (strncmp(options, "tls", 3) == 0) { + tls = 1; /* Require TLS */ +@@ -1978,6 +2126,22 @@ int vnc_display_open(DisplayState *ds, c + } + } + ++ /* ++ * Combinations we support here: ++ * ++ * - no-auth (clear text, no auth) ++ * - password (clear text, weak auth) ++ * - sasl (encrypt, good auth *IF* using Kerberos via GSSAPI) ++ * - tls (encrypt, weak anonymous creds, no auth) ++ * - tls + password (encrypt, weak anonymous creds, weak auth) ++ * - tls + sasl (encrypt, weak anonymous creds, good auth) ++ * - tls + x509 (encrypt, good x509 creds, no auth) ++ * - tls + x509 + password (encrypt, good x509 creds, weak auth) ++ * - tls + x509 + sasl (encrypt, good x509 creds, good auth) ++ * ++ * NB1. TLS is a stackable auth scheme. ++ * NB2. the x509 schemes have option to validate a client cert dname ++ */ + if (password) { + #ifdef CONFIG_VNC_TLS + if (tls) { +@@ -1990,13 +2154,34 @@ int vnc_display_open(DisplayState *ds, c + vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC; + } + } else { +-#endif ++#endif /* CONFIG_VNC_TLS */ + VNC_DEBUG("Initializing VNC server with password auth\n"); + vs->auth = VNC_AUTH_VNC; + #ifdef CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; + } +-#endif ++#endif /* CONFIG_VNC_TLS */ ++#ifdef CONFIG_VNC_SASL ++ } else if (sasl) { ++#ifdef CONFIG_VNC_TLS ++ if (tls) { ++ vs->auth = VNC_AUTH_VENCRYPT; ++ if (x509) { ++ 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"); ++ vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL; ++ } ++ } else { ++#endif /* CONFIG_VNC_TLS */ ++ VNC_DEBUG("Initializing VNC server with SASL auth\n"); ++ vs->auth = VNC_AUTH_SASL; ++#ifdef CONFIG_VNC_TLS ++ vs->subauth = VNC_AUTH_INVALID; ++ } ++#endif /* CONFIG_VNC_TLS */ ++#endif /* CONFIG_VNC_SASL */ + } else { + #ifdef CONFIG_VNC_TLS + if (tls) { +@@ -2018,6 +2203,16 @@ int vnc_display_open(DisplayState *ds, c + #endif + } + ++#ifdef CONFIG_VNC_SASL ++ if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) { ++ fprintf(stderr, "Failed to initialize SASL auth %s", ++ sasl_errstring(saslErr, NULL, NULL)); ++ free(vs->display); ++ vs->display = NULL; ++ return -1; ++ } ++#endif ++ + if (reverse) { + /* connect to viewer */ + if (strncmp(display, "unix:", 5) == 0) +diff -r 6981d4d832a9 vnc.h +--- a/vnc.h Mon Mar 02 11:13:33 2009 +0000 ++++ b/vnc.h Mon Mar 02 11:13:38 2009 +0000 +@@ -79,6 +79,10 @@ typedef struct VncDisplay VncDisplay; + #include "vnc-tls.h" + #include "vnc-auth-vencrypt.h" + #endif ++#ifdef CONFIG_VNC_SASL ++#include "vnc-auth-sasl.h" ++#endif ++ + + struct VncDisplay + { +@@ -118,10 +122,12 @@ struct VncState + int minor; + + char challenge[VNC_AUTH_CHALLENGE_SIZE]; +- + #ifdef CONFIG_VNC_TLS + VncStateTLS tls; + #endif ++#ifdef CONFIG_VNC_SASL ++ VncStateSASL sasl; ++#endif + + Buffer output; + Buffer input; +@@ -160,8 +166,9 @@ enum { + VNC_AUTH_RA2NE = 6, + VNC_AUTH_TIGHT = 16, + VNC_AUTH_ULTRA = 17, +- VNC_AUTH_TLS = 18, +- VNC_AUTH_VENCRYPT = 19 ++ VNC_AUTH_TLS = 18, /* Supported in GTK-VNC & VINO */ ++ VNC_AUTH_VENCRYPT = 19, /* Supported in GTK-VNC & VeNCrypt */ ++ VNC_AUTH_SASL = 20, /* Supported in GTK-VNC & VINO */ + }; + + enum { +@@ -172,6 +179,8 @@ enum { + VNC_AUTH_VENCRYPT_X509NONE = 260, + VNC_AUTH_VENCRYPT_X509VNC = 261, + VNC_AUTH_VENCRYPT_X509PLAIN = 262, ++ VNC_AUTH_VENCRYPT_X509SASL = 263, ++ VNC_AUTH_VENCRYPT_TLSSASL = 264, + }; + + +@@ -255,6 +264,8 @@ enum { + void vnc_client_read(void *opaque); + void vnc_client_write(void *opaque); + ++long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen); ++long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen); + + /* Protocol I/O functions */ + void vnc_write(VncState *vs, const void *data, size_t len); +@@ -274,8 +285,22 @@ uint32_t read_u32(uint8_t *data, size_t + + /* Protocol stage functions */ + void vnc_client_error(VncState *vs); ++int vnc_client_io_error(VncState *vs, int ret, int last_errno); + + void start_client_init(VncState *vs); + void start_auth_vnc(VncState *vs); + ++/* Buffer management */ ++void buffer_reserve(Buffer *buffer, size_t len); ++int buffer_empty(Buffer *buffer); ++uint8_t *buffer_end(Buffer *buffer); ++void buffer_reset(Buffer *buffer); ++void buffer_append(Buffer *buffer, const void *data, size_t len); ++ ++ ++/* Misc helpers */ ++ ++char *vnc_socket_local_addr(const char *format, int fd); ++char *vnc_socket_remote_addr(const char *format, int fd); ++ + #endif /* __QEMU_VNC_H */ diff --git a/qemu-sasl-07-vnc-monitor-authinfo.patch b/qemu-sasl-07-vnc-monitor-authinfo.patch new file mode 100644 index 0000000..be053e2 --- /dev/null +++ b/qemu-sasl-07-vnc-monitor-authinfo.patch @@ -0,0 +1,121 @@ +This patch extends the 'info vnc' monitor output to include information +about the VNC client authentication credentials. + +For clients authenticated using SASL, this will output the username. + +For clients authenticated using x509 certificates, this will output +the x509 distinguished name. + +Auth can be stacked, so both username & x509 dname may be shown. + + Server: + address: 0.0.0.0:5902 + auth: vencrypt+x509+sasl + Client: + address: 10.33.6.67:38621 + x509 dname: C=GB,O=ACME,L=London,ST=London,CN=localhost + username: admin + Client: + address: 10.33.6.63:38620 + x509 dname: C=GB,O=ACME,L=London,ST=London,CN=localhost + username: admin + + + + vnc-tls.c | 17 +++++++++++++++++ + vnc-tls.h | 3 +++ + vnc.c | 19 +++++++++++++++++-- + 3 files changed, 37 insertions(+), 2 deletions(-) + + Signed-off-by: Daniel P. Berrange + +diff -r 440be37a35ea vnc-tls.c +--- a/vnc-tls.c Fri Feb 20 11:46:26 2009 +0000 ++++ b/vnc-tls.c Fri Feb 20 11:47:52 2009 +0000 +@@ -241,6 +241,22 @@ int vnc_tls_validate_certificate(struct + return -1; + } + ++ if (i == 0) { ++ size_t dnameSize = 1024; ++ vs->tls.dname = qemu_malloc(dnameSize); ++ requery: ++ if ((ret = gnutls_x509_crt_get_dn (cert, vs->tls.dname, &dnameSize)) != 0) { ++ if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { ++ vs->tls.dname = qemu_realloc(vs->tls.dname, dnameSize); ++ goto requery; ++ } ++ gnutls_x509_crt_deinit (cert); ++ VNC_DEBUG("Cannot get client distinguished name: %s", ++ gnutls_strerror (ret)); ++ return -1; ++ } ++ } ++ + gnutls_x509_crt_deinit (cert); + } + +@@ -347,6 +363,7 @@ void vnc_tls_client_cleanup(struct VncSt + vs->tls.session = NULL; + } + vs->tls.wiremode = VNC_WIREMODE_CLEAR; ++ free(vs->tls.dname); + } + + +diff -r 440be37a35ea vnc-tls.h +--- a/vnc-tls.h Fri Feb 20 11:46:26 2009 +0000 ++++ b/vnc-tls.h Fri Feb 20 11:47:52 2009 +0000 +@@ -55,6 +55,9 @@ struct VncStateTLS { + /* Whether data is being TLS encrypted yet */ + int wiremode; + gnutls_session_t session; ++ ++ /* Client's Distinguished Name from the x509 cert */ ++ char *dname; + }; + + int vnc_tls_client_setup(VncState *vs, int x509Creds); +diff -r 440be37a35ea vnc.c +--- a/vnc.c Fri Feb 20 11:46:26 2009 +0000 ++++ b/vnc.c Fri Feb 20 11:47:52 2009 +0000 +@@ -156,6 +156,21 @@ static void do_info_vnc_client(VncState + term_puts("Client:\n"); + term_puts(clientAddr); + free(clientAddr); ++ ++#ifdef CONFIG_VNC_TLS ++ if (client->tls.session && ++ client->tls.dname) ++ term_printf(" x509 dname: %s\n", client->tls.dname); ++ else ++ term_puts(" x509 dname: none\n"); ++#endif ++#ifdef CONFIG_VNC_SASL ++ if (client->sasl.conn && ++ client->sasl.username) ++ term_printf(" username: %s\n", client->sasl.username); ++ else ++ term_puts(" username: none\n"); ++#endif + } + + void do_info_vnc(void) +@@ -1823,7 +1838,7 @@ static int protocol_client_auth(VncState + /* 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"; +@@ -1863,7 +1878,7 @@ static int protocol_client_auth(VncState + #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"; diff --git a/qemu-sasl-08-vnc-acl-mgmt.patch b/qemu-sasl-08-vnc-acl-mgmt.patch new file mode 100644 index 0000000..f02aae6 --- /dev/null +++ b/qemu-sasl-08-vnc-acl-mgmt.patch @@ -0,0 +1,769 @@ +This patch introduces a generic internal API for access control lists +to be used by network servers in QEMU. It adds support for checking +these ACL in the VNC server, in two places. The first ACL is for the +SASL authentication mechanism, checking the SASL username. This ACL +is called 'vnc.username'. The second is for the TLS authentication +mechanism, when x509 client certificates are turned on, checking against +the Distinguished Name of the client. This ACL is called 'vnc.x509dname' + +The internal API provides for an ACL with the following characteristics + + - A unique name, eg vnc.username, and vnc.x509dname. + - A default policy, allow or deny + - An ordered series of match rules, with allow or deny policy + +If none of the match rules apply, then the default policy is +used. + +There is a monitor API to manipulate the ACLs, which I'll describe via +examples + + (qemu) acl show vnc.username + policy: allow + (qemu) acl policy vnc.username denya + acl: policy set to 'deny' + (qemu) acl allow vnc.username fred + acl: added rule at position 1 + (qemu) acl allow vnc.username bob + acl: added rule at position 2 + (qemu) acl allow vnc.username joe 1 + acl: added rule at position 1 + (qemu) acl show vnc.username + policy: deny + 0: allow fred + 1: allow joe + 2: allow bob + + + (qemu) acl show vnc.x509dname + policy: allow + (qemu) acl policy vnc.x509dname deny + acl: policy set to 'deny' + (qemu) acl allow vnc.x509dname C=GB,O=ACME,L=London,CN=* + acl: added rule at position 1 + (qemu) acl allow vnc.x509dname C=GB,O=ACME,L=Boston,CN=bob + acl: added rule at position 2 + (qemu) acl show vnc.x509dname + policy: deny + 0: allow C=GB,O=ACME,L=London,CN=* + 1: allow C=GB,O=ACME,L=Boston,CN=bob + +By default the VNC server will not use any ACLs, allowing access to +the server if the user successfully authenticates. To enable use of +ACLs to restrict user access, the ',acl' flag should be given when +starting QEMU. The initial ACL activated will be a 'deny all' policy +and should be customized using monitor commands. + +eg enable SASL auth and ACLs + + qemu .... -vnc localhost:1,sasl,acl + +The next patch will provide a way to load a pre-defined ACL when +starting up + + + Makefile | 6 + + b/acl.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + b/acl.h | 74 ++++++++++++++++++++++ + configure | 18 +++++ + monitor.c | 95 ++++++++++++++++++++++++++++ + qemu-doc.texi | 49 ++++++++++++++ + vnc-auth-sasl.c | 16 +++- + vnc-auth-sasl.h | 7 ++ + vnc-tls.c | 19 +++++ + vnc-tls.h | 3 + vnc.c | 21 ++++++ + vnc.h | 3 + 12 files changed, 491 insertions(+), 5 deletions(-) + + Signed-off-by: Daniel P. Berrange + +diff -r b1008c5c6c16 Makefile +--- a/Makefile Mon Mar 02 11:13:40 2009 +0000 ++++ b/Makefile Mon Mar 02 12:11:04 2009 +0000 +@@ -144,7 +144,7 @@ endif + ifdef CONFIG_CURSES + OBJS+=curses.o + endif +-OBJS+=vnc.o d3des.o ++OBJS+=vnc.o acl.o d3des.o + ifdef CONFIG_VNC_TLS + OBJS+=vnc-tls.o vnc-auth-vencrypt.o + endif +@@ -174,9 +174,11 @@ sdl.o: sdl.c keymaps.h sdl_keysym.h + + sdl.o audio/sdlaudio.o: CFLAGS += $(SDL_CFLAGS) + ++acl.o: acl.h acl.c ++ + vnc.h: vnc-tls.h vnc-auth-vencrypt.h vnc-auth-sasl.h keymaps.h + +-vnc.o: vnc.c vnc.h vnc_keysym.h vnchextile.h d3des.c d3des.h ++vnc.o: vnc.c vnc.h vnc_keysym.h vnchextile.h d3des.c d3des.h acl.h + + vnc.o: CFLAGS += $(CONFIG_VNC_TLS_CFLAGS) + +diff -r b1008c5c6c16 acl.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/acl.c Mon Mar 02 12:11:04 2009 +0000 +@@ -0,0 +1,185 @@ ++/* ++ * QEMU access control list management ++ * ++ * Copyright (C) 2009 Red Hat, Inc ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++ ++#include "qemu-common.h" ++#include "sysemu.h" ++#include "acl.h" ++ ++#ifdef HAVE_FNMATCH_H ++#include ++#endif ++ ++ ++static unsigned int nacls = 0; ++static qemu_acl **acls = NULL; ++ ++ ++ ++qemu_acl *qemu_acl_find(const char *aclname) ++{ ++ int i; ++ for (i = 0 ; i < nacls ; i++) { ++ if (strcmp(acls[i]->aclname, aclname) == 0) ++ return acls[i]; ++ } ++ ++ return NULL; ++} ++ ++qemu_acl *qemu_acl_init(const char *aclname) ++{ ++ qemu_acl *acl; ++ ++ acl = qemu_acl_find(aclname); ++ if (acl) ++ return acl; ++ ++ acl = qemu_malloc(sizeof(*acl)); ++ acl->aclname = qemu_strdup(aclname); ++ /* Deny by default, so there is no window of "open ++ * access" between QEMU starting, and the user setting ++ * up ACLs in the monitor */ ++ acl->defaultDeny = 1; ++ ++ acl->nentries = 0; ++ TAILQ_INIT(&acl->entries); ++ ++ acls = qemu_realloc(acls, sizeof(*acls) * (nacls +1)); ++ acls[nacls] = acl; ++ nacls++; ++ ++ return acl; ++} ++ ++int qemu_acl_party_is_allowed(qemu_acl *acl, ++ const char *party) ++{ ++ qemu_acl_entry *entry; ++ ++ TAILQ_FOREACH(entry, &acl->entries, next) { ++#ifdef HAVE_FNMATCH_H ++ if (fnmatch(entry->match, party, 0) == 0) ++ return entry->deny ? 0 : 1; ++#else ++ /* No fnmatch, so fallback to exact string matching ++ * instead of allowing wildcards */ ++ if (strcmp(entry->match, party) == 0) ++ return entry->deny ? 0 : 1; ++#endif ++ } ++ ++ return acl->defaultDeny ? 0 : 1; ++} ++ ++ ++void qemu_acl_reset(qemu_acl *acl) ++{ ++ qemu_acl_entry *entry; ++ ++ /* Put back to deny by default, so there is no window ++ * of "open access" while the user re-initializes the ++ * access control list */ ++ acl->defaultDeny = 1; ++ TAILQ_FOREACH(entry, &acl->entries, next) { ++ TAILQ_REMOVE(&acl->entries, entry, next); ++ free(entry->match); ++ free(entry); ++ } ++ acl->nentries = 0; ++} ++ ++ ++int qemu_acl_append(qemu_acl *acl, ++ int deny, ++ const char *match) ++{ ++ qemu_acl_entry *entry; ++ ++ entry = qemu_malloc(sizeof(*entry)); ++ entry->match = qemu_strdup(match); ++ entry->deny = deny; ++ ++ TAILQ_INSERT_TAIL(&acl->entries, entry, next); ++ acl->nentries++; ++ ++ return acl->nentries; ++} ++ ++ ++int qemu_acl_insert(qemu_acl *acl, ++ int deny, ++ const char *match, ++ int index) ++{ ++ qemu_acl_entry *entry; ++ qemu_acl_entry *tmp; ++ int i = 0; ++ ++ if (index <= 0) ++ return -1; ++ if (index >= acl->nentries) ++ return qemu_acl_append(acl, deny, match); ++ ++ ++ entry = qemu_malloc(sizeof(*entry)); ++ entry->match = qemu_strdup(match); ++ entry->deny = deny; ++ ++ TAILQ_FOREACH(tmp, &acl->entries, next) { ++ i++; ++ if (i == index) { ++ TAILQ_INSERT_BEFORE(tmp, entry, next); ++ acl->nentries++; ++ break; ++ } ++ } ++ ++ return i; ++} ++ ++int qemu_acl_remove(qemu_acl *acl, ++ const char *match) ++{ ++ qemu_acl_entry *entry; ++ int i = 0; ++ ++ TAILQ_FOREACH(entry, &acl->entries, next) { ++ i++; ++ if (strcmp(entry->match, match) == 0) { ++ TAILQ_REMOVE(&acl->entries, entry, next); ++ return i; ++ } ++ } ++ return -1; ++} ++ ++ ++/* ++ * Local variables: ++ * c-indent-level: 4 ++ * c-basic-offset: 4 ++ * tab-width: 8 ++ * End: ++ */ +diff -r b1008c5c6c16 acl.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/acl.h Mon Mar 02 12:11:04 2009 +0000 +@@ -0,0 +1,74 @@ ++/* ++ * QEMU access control list management ++ * ++ * Copyright (C) 2009 Red Hat, Inc ++ * ++ * 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 ++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ * copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ++ * THE SOFTWARE. ++ */ ++ ++#ifndef __QEMU_ACL_H__ ++#define __QEMU_ACL_H__ ++ ++#include "sys-queue.h" ++ ++typedef struct qemu_acl_entry qemu_acl_entry; ++typedef struct qemu_acl qemu_acl; ++ ++struct qemu_acl_entry { ++ char *match; ++ int deny; ++ ++ TAILQ_ENTRY(qemu_acl_entry) next; ++}; ++ ++struct qemu_acl { ++ char *aclname; ++ unsigned int nentries; ++ TAILQ_HEAD(,qemu_acl_entry) entries; ++ int defaultDeny; ++}; ++ ++qemu_acl *qemu_acl_init(const char *aclname); ++ ++qemu_acl *qemu_acl_find(const char *aclname); ++ ++int qemu_acl_party_is_allowed(qemu_acl *acl, ++ const char *party); ++ ++void qemu_acl_reset(qemu_acl *acl); ++ ++int qemu_acl_append(qemu_acl *acl, ++ int deny, ++ const char *match); ++int qemu_acl_insert(qemu_acl *acl, ++ int deny, ++ const char *match, ++ int index); ++int qemu_acl_remove(qemu_acl *acl, ++ const char *match); ++ ++#endif /* __QEMU_ACL_H__ */ ++ ++/* ++ * Local variables: ++ * c-indent-level: 4 ++ * c-basic-offset: 4 ++ * tab-width: 8 ++ * End: ++ */ +diff -r b1008c5c6c16 configure +--- a/configure Mon Mar 02 11:13:40 2009 +0000 ++++ b/configure Mon Mar 02 12:11:04 2009 +0000 +@@ -846,6 +846,21 @@ EOF + fi + + ########################################## ++# fnmatch() probe, used for ACL routines ++fnmatch="no" ++cat > $TMPC << EOF ++#include ++int main(void) ++{ ++ fnmatch("foo", "foo", 0); ++ return 0; ++} ++EOF ++if $cc $ARCH_CFLAGS -o $TMPE $TMPC > /dev/null 2> /dev/null ; then ++ fnmatch="yes" ++fi ++ ++########################################## + # vde libraries probe + if test "$vde" = "yes" ; then + cat > $TMPC << EOF +@@ -1405,6 +1420,9 @@ if test "$vnc_sasl" = "yes" ; then + echo "CONFIG_VNC_SASL_LIBS=$vnc_sasl_libs" >> $config_mak + echo "#define CONFIG_VNC_SASL 1" >> $config_h + fi ++if test "$fnmatch" = "yes" ; then ++ echo "#define HAVE_FNMATCH_H 1" >> $config_h ++fi + qemu_version=`head $source_path/VERSION` + echo "VERSION=$qemu_version" >>$config_mak + echo "#define QEMU_VERSION \"$qemu_version\"" >> $config_h +diff -r b1008c5c6c16 monitor.c +--- a/monitor.c Mon Mar 02 11:13:40 2009 +0000 ++++ b/monitor.c Mon Mar 02 12:11:04 2009 +0000 +@@ -39,6 +39,7 @@ + #include "qemu-timer.h" + #include "migration.h" + #include "kvm.h" ++#include "acl.h" + + //#define DEBUG + //#define DEBUG_COMPLETION +@@ -1425,6 +1426,85 @@ static void do_info_balloon(void) + term_printf("balloon: actual=%d\n", (int)(actual >> 20)); + } + ++static void do_acl(const char *command, ++ const char *aclname, ++ const char *match, ++ int has_index, ++ int index) ++{ ++ qemu_acl *acl; ++ ++ acl = qemu_acl_find(aclname); ++ if (!acl) { ++ term_printf("acl: unknown list '%s'\n", aclname); ++ return; ++ } ++ ++ if (strcmp(command, "show") == 0) { ++ int i = 0; ++ qemu_acl_entry *entry; ++ term_printf("policy: %s\n", ++ acl->defaultDeny ? "deny" : "allow"); ++ TAILQ_FOREACH(entry, &acl->entries, next) { ++ i++; ++ term_printf("%d: %s %s\n", i, ++ entry->deny ? "deny" : "allow", ++ entry->match); ++ } ++ } else if (strcmp(command, "reset") == 0) { ++ qemu_acl_reset(acl); ++ term_printf("acl: removed all rules\n"); ++ } else if (strcmp(command, "policy") == 0) { ++ if (!match) { ++ term_printf("acl: missing policy parameter\n"); ++ return; ++ } ++ ++ if (strcmp(match, "allow") == 0) { ++ acl->defaultDeny = 0; ++ term_printf("acl: policy set to 'allow'\n"); ++ } else if (strcmp(match, "deny") == 0) { ++ acl->defaultDeny = 1; ++ term_printf("acl: policy set to 'deny'\n"); ++ } else { ++ term_printf("acl: unknown policy '%s', expected 'deny' or 'allow'\n", match); ++ } ++ } else if ((strcmp(command, "allow") == 0) || ++ (strcmp(command, "deny") == 0)) { ++ int deny = strcmp(command, "deny") == 0 ? 1 : 0; ++ int ret; ++ ++ if (!match) { ++ term_printf("acl: missing match parameter\n"); ++ return; ++ } ++ ++ if (has_index) ++ ret = qemu_acl_insert(acl, deny, match, index); ++ else ++ ret = qemu_acl_append(acl, deny, match); ++ if (ret < 0) ++ term_printf("acl: unable to add acl entry\n"); ++ else ++ term_printf("acl: added rule at position %d\n", ret); ++ } else if (strcmp(command, "remove") == 0) { ++ int ret; ++ ++ if (!match) { ++ term_printf("acl: missing match parameter\n"); ++ return; ++ } ++ ++ ret = qemu_acl_remove(acl, match); ++ if (ret < 0) ++ term_printf("acl: no matching acl entry\n"); ++ else ++ term_printf("acl: removed rule at position %d\n", ret); ++ } else { ++ term_printf("acl: unknown command '%s'\n", command); ++ } ++} ++ + /* Please update qemu-doc.texi when adding or changing commands */ + static const term_cmd_t term_cmds[] = { + { "help|?", "s?", do_help, +@@ -1529,6 +1609,12 @@ static const term_cmd_t term_cmds[] = { + "target", "request VM to change it's memory allocation (in MB)" }, + { "set_link", "ss", do_set_link, + "name [up|down]", "change the link status of a network adapter" }, ++ { "acl", "sss?i?", do_acl, " [] []\n", ++ "acl show vnc.username\n" ++ "acl policy vnc.username deny\n" ++ "acl allow vnc.username fred\n" ++ "acl deny vnc.username bob\n" ++ "acl reset vnc.username\n" }, + { NULL, NULL, }, + }; + +@@ -2891,3 +2977,12 @@ void monitor_readline(const char *prompt + monitor_hd[i]->focus = old_focus[i]; + } + } ++ ++ ++/* ++ * Local variables: ++ * c-indent-level: 4 ++ * c-basic-offset: 4 ++ * tab-width: 8 ++ * End: ++ */ +diff -r b1008c5c6c16 qemu-doc.texi +--- a/qemu-doc.texi Mon Mar 02 11:13:40 2009 +0000 ++++ b/qemu-doc.texi Mon Mar 02 12:11:04 2009 +0000 +@@ -631,6 +631,19 @@ ensures a data encryption preventing com + credentials. See the @ref{vnc_security} section for details on using + SASL authentication. + ++@item acl ++ ++Turn on access control lists for checking of the x509 client certificate ++and SASL party. For x509 certs, the ACL check is made against the ++certificate's distinguished name. This is something that looks like ++@code{C=GB,O=ACME,L=Boston,CN=bob}. For SASL party, the ACL check is ++made against the username, which depending on the SASL plugin, may ++include a realm component, eg @code{bob} or @code{bob\@EXAMPLE.COM}. ++When the @option{acl} flag is set, the initial access list will be ++empty, with a @code{deny} policy. Thus no one will be allowed to ++use the VNC server until the ACLs have been loaded. This can be ++achieved using the @code{acl} monitor command. ++ + @end table + + @end table +@@ -1392,6 +1405,42 @@ Password: ******** + + @end table + ++@item acl @var{subcommand} @var{aclname} @var{match} @var{index} ++ ++Manage access control lists for network services. There are currently ++two named access control lists, @var{vnc.x509dname} and @var{vnc.username} ++matching on the x509 client certificate distinguished name, and SASL ++username respectively. ++ ++@table @option ++@item acl show ++list all the match rules in the access control list, and the default ++policy ++@item acl policy @code{allow|deny} ++set the default access control list policy, used in the event that ++none of the explicit rules match. The default policy at startup is ++always @code{deny} ++@item acl allow [] ++add a match to the access control list, allowing access. The match will ++normally be an exact username or x509 distinguished name, but can ++optionally include wildcard globs. eg @code{*\@EXAMPLE.COM} to allow ++all users in the @code{EXAMPLE.COM} kerberos realm. The match will ++normally be appended to the end of the ACL, but can be inserted ++earlier in the list if the optional @code{index} parameter is supplied. ++@item acl deny [] ++add a match to the access control list, denying access. The match will ++normally be an exact username or x509 distinguished name, but can ++optionally include wildcard globs. eg @code{*\@EXAMPLE.COM} to allow ++all users in the @code{EXAMPLE.COM} kerberos realm. The match will ++normally be appended to the end of the ACL, but can be inserted ++earlier in the list if the optional @code{index} parameter is supplied. ++@item acl remove ++remove the specified match rule from the access control list. ++@item acl reset ++remove all matches from the access control list, and set the default ++policy back to @code{deny}. ++@end table ++ + @item screendump @var{filename} + Save screen into PPM image @var{filename}. + +diff -r b1008c5c6c16 vnc-auth-sasl.c +--- a/vnc-auth-sasl.c Mon Mar 02 11:13:40 2009 +0000 ++++ b/vnc-auth-sasl.c Mon Mar 02 12:11:04 2009 +0000 +@@ -120,22 +120,32 @@ static int vnc_auth_sasl_check_access(Vn + { + const void *val; + int err; ++ int allow; + + err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val); + if (err != SASL_OK) { +- VNC_DEBUG("cannot query SASL username on connection %d (%s)\n", ++ VNC_DEBUG("cannot query SASL username on connection %d (%s), denying access\n", + err, sasl_errstring(err, NULL, NULL)); + return -1; + } + if (val == NULL) { +- VNC_DEBUG("no client username was found\n"); ++ VNC_DEBUG("no client username was found, denying access\n"); + return -1; + } + VNC_DEBUG("SASL client username %s\n", (const char *)val); + + vs->sasl.username = qemu_strdup((const char*)val); + +- return 0; ++ if (vs->vd->sasl.acl == NULL) { ++ VNC_DEBUG("no ACL activated, allowing access\n"); ++ return 0; ++ } ++ ++ allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username); ++ ++ VNC_DEBUG("SASL client %s %s by ACL\n", vs->sasl.username, ++ allow ? "allowed" : "denied"); ++ return allow ? 0 : -1; + } + + static int vnc_auth_sasl_check_ssf(VncState *vs) +diff -r b1008c5c6c16 vnc-auth-sasl.h +--- a/vnc-auth-sasl.h Mon Mar 02 11:13:40 2009 +0000 ++++ b/vnc-auth-sasl.h Mon Mar 02 12:11:04 2009 +0000 +@@ -30,6 +30,9 @@ + #include + + typedef struct VncStateSASL VncStateSASL; ++typedef struct VncDisplaySASL VncDisplaySASL; ++ ++#include "acl.h" + + struct VncStateSASL { + sasl_conn_t *conn; +@@ -56,6 +59,10 @@ struct VncStateSASL { + char *mechlist; + }; + ++struct VncDisplaySASL { ++ qemu_acl *acl; ++}; ++ + void vnc_sasl_client_cleanup(VncState *vs); + + long vnc_client_read_sasl(VncState *vs); +diff -r b1008c5c6c16 vnc-tls.c +--- a/vnc-tls.c Mon Mar 02 11:13:40 2009 +0000 ++++ b/vnc-tls.c Mon Mar 02 12:11:04 2009 +0000 +@@ -255,6 +255,25 @@ int vnc_tls_validate_certificate(struct + gnutls_strerror (ret)); + return -1; + } ++ ++ if (vs->vd->tls.x509verify) { ++ int allow; ++ if (!vs->vd->tls.acl) { ++ VNC_DEBUG("no ACL activated, allowing access"); ++ gnutls_x509_crt_deinit (cert); ++ continue; ++ } ++ ++ allow = qemu_acl_party_is_allowed(vs->vd->tls.acl, ++ vs->tls.dname); ++ ++ VNC_DEBUG("TLS x509 ACL check for %s is %s\n", ++ vs->tls.dname, allow ? "allowed" : "denied"); ++ if (!allow) { ++ gnutls_x509_crt_deinit (cert); ++ return -1; ++ } ++ } + } + + gnutls_x509_crt_deinit (cert); +diff -r b1008c5c6c16 vnc-tls.h +--- a/vnc-tls.h Mon Mar 02 11:13:40 2009 +0000 ++++ b/vnc-tls.h Mon Mar 02 12:11:04 2009 +0000 +@@ -31,6 +31,8 @@ + #include + #include + ++#include "acl.h" ++ + enum { + VNC_WIREMODE_CLEAR, + VNC_WIREMODE_TLS, +@@ -42,6 +44,7 @@ typedef struct VncStateTLS VncStateTLS; + /* Server state */ + struct VncDisplayTLS { + int x509verify; /* Non-zero if server requests & validates client cert */ ++ qemu_acl *acl; + + /* Paths to x509 certs/keys */ + char *x509cacert; +diff -r b1008c5c6c16 vnc.c +--- a/vnc.c Mon Mar 02 11:13:40 2009 +0000 ++++ b/vnc.c Mon Mar 02 12:11:04 2009 +0000 +@@ -28,6 +28,7 @@ + #include "sysemu.h" + #include "qemu_socket.h" + #include "qemu-timer.h" ++#include "acl.h" + + #define VNC_REFRESH_INTERVAL (1000 / 30) + +@@ -2082,6 +2083,7 @@ int vnc_display_open(DisplayState *ds, c + int sasl = 0; + int saslErr; + #endif ++ int acl = 0; + + if (!vnc_display) + return -1; +@@ -2138,9 +2140,28 @@ int vnc_display_open(DisplayState *ds, c + return -1; + } + #endif ++ } else if (strncmp(options, "acl", 3) == 0) { ++ acl = 1; + } + } + ++#ifdef CONFIG_VNC_TLS ++ 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: + * +diff -r b1008c5c6c16 vnc.h +--- a/vnc.h Mon Mar 02 11:13:40 2009 +0000 ++++ b/vnc.h Mon Mar 02 12:11:04 2009 +0000 +@@ -98,6 +98,9 @@ struct VncDisplay + int subauth; /* Used by VeNCrypt */ + VncDisplayTLS tls; + #endif ++#ifdef CONFIG_VNC_SASL ++ VncDisplaySASL sasl; ++#endif + }; + + struct VncState diff --git a/qemu.spec b/qemu.spec index 7a86eb0..e2eabe8 100644 --- a/qemu.spec +++ b/qemu.spec @@ -1,14 +1,29 @@ Summary: QEMU is a FAST! processor emulator Name: qemu Version: 1.0 -Release: 0.4.svn6666%{?dist} +Release: 0.5.svn6666%{?dist} License: GPLv2+ and LGPLv2+ Group: Development/Tools URL: http://www.qemu.org/ Source0: http://www.qemu.org/%{name}-%{version}.tar.gz Source1: qemu.init + +# VNC SASL authentication support +# Not upstream yet, but approved for commit immediately +# after this release +Patch1: qemu-sasl-01-tls-handshake-fix.patch +Patch2: qemu-sasl-02-vnc-monitor-info.patch +Patch3: qemu-sasl-03-display-keymaps.patch +Patch4: qemu-sasl-04-vnc-struct.patch +Patch5: qemu-sasl-05-vnc-tls-vencrypt.patch +Patch6: qemu-sasl-06-vnc-sasl.patch +Patch7: qemu-sasl-07-vnc-monitor-authinfo.patch +Patch8: qemu-sasl-08-vnc-acl-mgmt.patch +# NB, delibrately not including patch 09 which is not +# intended for commit + BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -BuildRequires: SDL-devel zlib-devel which texi2html gnutls-devel +BuildRequires: SDL-devel zlib-devel which texi2html gnutls-devel cyrus-sasl-devel Requires: %{name}-user = %{version}-%{release} Requires: %{name}-system-x86 = %{version}-%{release} Requires: %{name}-system-sparc = %{version}-%{release} @@ -78,7 +93,7 @@ Requires: %{name}-common = %{version}-%{release} %qemupkg system-x86 {system emulator for x86} Requires: etherboot-zroms-kvm Requires: vgabios -Requires: bochs-bios +Requires: bochs-bios-data %qemudesc system-x86 {system emulator for x86} %qemupkgdesc system-ppc {system emulator for ppc} @@ -93,6 +108,22 @@ Requires: bochs-bios %prep %setup -q +# 01-tls-handshake-fix +%patch1 -p1 +# 02-vnc-monitor-info +%patch2 -p1 +# 03-display-keymaps +%patch3 -p1 +# 04-vnc-struct +%patch4 -p1 +# 05-vnc-tls-vencrypt +%patch5 -p1 +# 06-vnc-sasl +%patch6 -p1 +# 07-vnc-monitor-authinfo +%patch7 -p1 +# 08-vnc-acl-mgmt +%patch8 -p1 %build # systems like rhel build system does not have a recent enough linker so @@ -132,6 +163,8 @@ chmod -x ${RPM_BUILD_ROOT}%{_mandir}/man1/* install -D -p -m 0755 %{SOURCE1} $RPM_BUILD_ROOT%{_sysconfdir}/rc.d/init.d/qemu install -D -p -m 0644 -t ${RPM_BUILD_ROOT}/%{qemudocdir} Changelog README TODO COPYING COPYING.LIB LICENSE +install -D -p -m 0644 qemu.sasl $RPM_BUILD_ROOT%{_sysconfdir}/sasl2/qemu.conf + rm -rf ${RPM_BUILD_ROOT}/usr/share//qemu/pxe*bin rm -rf ${RPM_BUILD_ROOT}/usr/share//qemu/vgabios*bin rm -rf ${RPM_BUILD_ROOT}/usr/share//qemu/bios.bin @@ -187,6 +220,7 @@ fi %{_mandir}/man1/qemu.1* %{_mandir}/man8/qemu-nbd.8* %{_bindir}/qemu-nbd +%config(noreplace) %{_sysconfdir}/sasl2/qemu.conf %files user %defattr(-,root,root) %{_sysconfdir}/rc.d/init.d/qemu @@ -250,6 +284,10 @@ fi %{_mandir}/man1/qemu-img.1* %changelog +* Tue Mar 3 2009 Daniel P. Berrange - 1.0-0.5.svn6666 +- Support VNC SASL authentication protocol +- Fix dep on bochs-bios-data + * Tue Mar 03 2009 Glauber Costa - 1.0-0.4.svn6666 - use bios from bochs-bios package.