diff --git a/Makefile b/Makefile index e8d1a5cdc..e9a75c422 100644 --- a/Makefile +++ b/Makefile @@ -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 --git a/Makefile.target b/Makefile.target index f33f7621f..9a2f123d3 100644 --- a/Makefile.target +++ b/Makefile.target @@ -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 --git a/configure b/configure index faacb8de0..5c2b15447 100755 --- a/configure +++ b/configure @@ -164,6 +164,7 @@ fmod_lib="" fmod_inc="" oss_lib="" vnc_tls="yes" +vnc_sasl="yes" bsd="no" linux="no" solaris="no" @@ -388,6 +389,8 @@ for opt do ;; --disable-vnc-tls) vnc_tls="no" ;; + --disable-vnc-sasl) vnc_sasl="no" + ;; --disable-slirp) slirp="no" ;; --disable-vde) vde="no" @@ -545,6 +548,7 @@ echo " Available cards: $audio_possible_cards" 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" @@ -838,6 +842,25 @@ EOF fi 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 @@ -1146,6 +1169,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 @@ -1387,6 +1415,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 --git a/qemu-doc.texi b/qemu-doc.texi index 616de48c0..1528f39cf 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -616,6 +616,21 @@ path following this option specifies where the x509 certificates are to 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 deployment scenarios. * 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 --git a/qemu.sasl b/qemu.sasl new file mode 100644 index 000000000..cf19cf8d0 --- /dev/null +++ b/qemu.sasl @@ -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 --git a/vnc-auth-sasl.c b/vnc-auth-sasl.c new file mode 100644 index 000000000..2882a357d --- /dev/null +++ b/vnc-auth-sasl.c @@ -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 --git a/vnc-auth-sasl.h b/vnc-auth-sasl.h new file mode 100644 index 000000000..a72973a26 --- /dev/null +++ b/vnc-auth-sasl.h @@ -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 --git a/vnc.c b/vnc.c index f4d8fe60e..074238790 100644 --- a/vnc.c +++ b/vnc.c @@ -68,7 +68,8 @@ static char *addr_to_string(const char *format, 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 char *format, int fd) { 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(VncDisplay *vd) { return "vencrypt+x509+vnc"; case VNC_AUTH_VENCRYPT_X509PLAIN: return "vencrypt+x509+plain"; + case VNC_AUTH_VENCRYPT_TLSSASL: + return "vencrypt+tls+sasl"; + case VNC_AUTH_VENCRYPT_X509SASL: + return "vencrypt+x509+sasl"; default: return "vencrypt"; } #else return "vencrypt"; #endif + case VNC_AUTH_SASL: + return "sasl"; } return "unknown"; } @@ -278,7 +286,7 @@ static void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, 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 *buffer, size_t len) } } -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; @@ -822,7 +830,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) { @@ -848,6 +857,9 @@ static int vnc_client_io_error(VncState *vs, int ret, int last_errno) #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; @@ -878,14 +890,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; @@ -895,10 +921,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; @@ -906,6 +964,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) @@ -914,16 +995,28 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) 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; @@ -933,13 +1026,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; @@ -1723,6 +1856,13 @@ static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len) 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); @@ -1924,6 +2064,10 @@ int vnc_display_open(DisplayState *ds, const char *display) #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; @@ -1943,6 +2087,10 @@ int vnc_display_open(DisplayState *ds, const char *display) 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 */ @@ -1979,6 +2127,22 @@ int vnc_display_open(DisplayState *ds, const char *display) } } + /* + * 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) { @@ -1991,13 +2155,34 @@ int vnc_display_open(DisplayState *ds, const char *display) 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) { @@ -2019,6 +2204,16 @@ int vnc_display_open(DisplayState *ds, const char *display) #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 --git a/vnc.h b/vnc.h index b5ae1f951..59a1b4ab9 100644 --- a/vnc.h +++ b/vnc.h @@ -80,6 +80,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 { @@ -119,10 +123,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; @@ -161,8 +167,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 { @@ -173,6 +180,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, }; @@ -256,6 +265,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); @@ -275,8 +286,22 @@ uint32_t read_u32(uint8_t *data, size_t offset); /* 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 */