[gtk-vnc-devel] PATCH: RFC: Support SASL authentication extension



This patch is the counterpart to one I just sent to qemu-devel to
add support for a new SASL authentication type. This was recently
assigned an offiical auth code

http://realvnc.com/pipermail/vnc-list/2008-December/059463.html

And the protocol is described here:

http://realvnc.com/pipermail/vnc-list/2008-December/059462.html


This is pretty much just a straight impl of this protocol into
GTK-VNC. It fully supports SASL SSF layers, so suitable SASL
mechanisms (like GSSAPI) can provide encryption as well as 
authentication. Alternatively, it will layer SASL over the 
existing VeNCrypt extension which uses TLS.

This is hooked up with our existing auth callback support, so
it will work with any SASL mechanims requiring passwords, or
usernames. We can add more callbacks if it is determined to 
be needed. GSSAPI doesn't need any callbacks, automatically
locating your Kerberos principle.

The impl uses Cyrus-SASL which is common on all Linux boxes.
If not present, the SASL extension will be disabled.

 configure.ac          |   47 ++
 examples/gvncviewer.c |    4 
 src/Makefile.am       |    4 
 src/gvnc.c            |  919 ++++++++++++++++++++++++++++++++++++++++++--------
 src/gvnc.h            |    6 
 src/vncdisplay.c      |   14 
 6 files changed, 852 insertions(+), 142 deletions(-)


Daniel


diff -r d68935d582f0 configure.ac
--- a/configure.ac	Sun Dec 07 19:56:56 2008 +0000
+++ b/configure.ac	Wed Feb 04 17:26:10 2009 +0000
@@ -140,16 +140,63 @@ fi
 AC_DEFINE_UNQUOTED(WITH_LIBVIEW,[$WITH_LIBVIEW], [Whether to use libview])
 AC_SUBST(VIEW_CFLAGS)
 AC_SUBST(VIEW_LIBS)
 
 PKG_CHECK_MODULES(GNUTLS, gnutls >= $GNUTLS_REQUIRED)
 AC_SUBST(GNUTLS_CFLAGS)
 AC_SUBST(GNUTLS_LIBS)
 
+dnl Cyrus SASL
+AC_ARG_WITH([sasl],
+  [  --with-sasl         use cyrus SASL for authentication],
+  [],
+  [with_sasl=check])
+
+SASL_CFLAGS=
+SASL_LIBS=
+if test "x$with_sasl" != "xno"; then
+  if test "x$with_sasl" != "xyes" -a "x$with_sasl" != "xcheck"; then
+    SASL_CFLAGS="-I$with_sasl"
+    SASL_LIBS="-L$with_sasl"
+  fi
+  fail=0
+  old_cflags="$CFLAGS"
+  old_libs="$LIBS"
+  CFLAGS="$CFLAGS $SASL_CFLAGS"
+  LIBS="$LIBS $SASL_LIBS"
+  AC_CHECK_HEADER([sasl/sasl.h],[],[
+    if test "x$with_sasl" != "xcheck" ; then
+        with_sasl=no
+    else
+        fail=1
+    fi])
+  if test "x$with_sasl" != "xno" ; then
+    AC_CHECK_LIB([sasl2], [sasl_client_init],[with_sasl=yes],[
+      if test "x$with_sasl" = "xcheck" ; then
+          with_sasl=no
+      else
+          fail=1
+      fi])
+  fi
+  test $fail = 1 &&
+    AC_MSG_ERROR([You must install the Cyrus SASL development package in order to compile GTK-VNC])
+  CFLAGS="$old_cflags"
+  LIBS="$old_libs"
+  SASL_LIBS="$SASL_LIBS -lsasl2"
+  if test "x$with_sasl" = "xyes" ; then
+    AC_DEFINE_UNQUOTED([HAVE_SASL], 1,
+      [whether Cyrus SASL is available for authentication])
+  fi
+fi
+AM_CONDITIONAL([HAVE_SASL], [test "x$with_sasl" = "xyes"])
+AC_SUBST([SASL_CFLAGS])
+AC_SUBST([SASL_LIBS])
+
+
 GTHREAD_CFLAGS=
 GTHREAD_LIBS=
 
 AC_CHECK_LIB(z, inflate, [], [AC_MSG_ERROR([zlib not found])])
 
 WITH_UCONTEXT=1
 
 AC_ARG_WITH(coroutine,
diff -r d68935d582f0 examples/gvncviewer.c
--- a/examples/gvncviewer.c	Sun Dec 07 19:56:56 2008 +0000
+++ b/examples/gvncviewer.c	Wed Feb 04 17:26:10 2009 +0000
@@ -235,18 +235,22 @@ static void vnc_credential(GtkWidget *vn
 
 		if (response == GTK_RESPONSE_OK) {
 			for (i = 0, row = 0 ; i < credList->n_values ; i++) {
 				GValue *cred = g_value_array_get_nth(credList, i);
 				switch (g_value_get_enum(cred)) {
 				case VNC_DISPLAY_CREDENTIAL_USERNAME:
 				case VNC_DISPLAY_CREDENTIAL_PASSWORD:
 					data[i] = gtk_entry_get_text(GTK_ENTRY(entry[row]));
+					fprintf(stderr, "**********o %s\n", data[i]);
 					break;
+				default:
+					continue;
 				}
+				row++;
 			}
 		}
 	}
 
 	for (i = 0 ; i < credList->n_values ; i++) {
 		GValue *cred = g_value_array_get_nth(credList, i);
 		if (data[i]) {
 			if (vnc_display_set_credential(VNC_DISPLAY(vncdisplay),
diff -r d68935d582f0 src/Makefile.am
--- a/src/Makefile.am	Sun Dec 07 19:56:56 2008 +0000
+++ b/src/Makefile.am	Wed Feb 04 17:26:10 2009 +0000
@@ -1,18 +1,18 @@
 
 EXTRA_DIST = libgtk-vnc_sym.version vncmarshal.txt
 
 lib_LTLIBRARIES = libgtk-vnc-1.0.la
 
 libgtk_vnc_1_0_la_LIBADD = @GTK_LIBS@ @GNUTLS_LIBS@ \
-			   @GTHREAD_LIBS@ \
+			   @GTHREAD_LIBS@ @SASL_LIBS@ \
                            ../gnulib/lib/libgnu.la
 libgtk_vnc_1_0_la_CFLAGS = @GTK_CFLAGS@ @GNUTLS_CFLAGS@ \
-			   @GTHREAD_CFLAGS@ @WARNING_CFLAGS@ \
+			   @GTHREAD_CFLAGS@ @SASL_CFLAGS@ @WARNING_CFLAGS@ \
 			   -DSYSCONFDIR=\""$(sysconfdir)"\" \
                            -DG_LOG_DOMAIN=\"gtk-vnc\" \
                            -I$(top_srcdir)/gnulib/lib -I../gnulib/lib
 libgtk_vnc_1_0_la_LDFLAGS = -Wl, @LD_VERSION_SCRIPT_SUPPORT@ \
                             -version-info 0:1:0 -no-undefined
 
 gtk_vnc_includedir = $(includedir)/gtk-vnc-1.0/
 gtk_vnc_include_HEADERS = vncdisplay.h
diff -r d68935d582f0 src/gvnc.c
--- a/src/gvnc.c	Sun Dec 07 19:56:56 2008 +0000
+++ b/src/gvnc.c	Wed Feb 04 17:26:10 2009 +0000
@@ -41,16 +41,20 @@
 #include "d3des.h"
 
 #include "x_keymap.h"
 
 #include "utils.h"
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
 
+#ifdef HAVE_SASL
+#include <sasl/sasl.h>
+#endif
+
 #include <zlib.h>
 
 #include <gdk/gdkkeysyms.h>
 
 #include "getaddrinfo.h"
 
 /* AI_ADDRCONFIG is missing on some systems and gnulib won't provide it
    even if its emulated getaddrinfo() for us . */
@@ -128,16 +132,26 @@ struct gvnc
 	unsigned int auth_type;
 	unsigned int auth_subtype;
 	char *cred_username;
 	char *cred_password;
 	char *cred_x509_cacert;
 	char *cred_x509_cacrl;
 	char *cred_x509_cert;
 	char *cred_x509_key;
+	gboolean want_cred_username;
+	gboolean want_cred_password;
+	gboolean want_cred_x509;
+
+#if HAVE_SASL
+	sasl_conn_t *saslconn;      /* SASL context */
+	const char *saslDecoded;
+	unsigned int saslDecodedLength;
+	unsigned int saslDecodedOffset;
+#endif
 
 	char read_buffer[4096];
 	size_t read_offset;
 	size_t read_size;
 
 	char write_buffer[4096];
 	size_t write_offset;
 
@@ -350,16 +364,143 @@ static int gvnc_zread(struct gvnc *gvnc,
 		}
 	}
 
 	return offset;
 }
 
 /* IO functions */
 
+
+/*
+ * Read at least 1 more byte of data straight off the wire
+ * into the requested buffer.
+ */
+static int gvnc_read_wire(struct gvnc *gvnc, void *data, size_t len)
+{
+	int ret;
+
+ reread:
+	if (gvnc->tls_session) {
+		ret = gnutls_read(gvnc->tls_session, data, len);
+		if (ret < 0) {
+			if (ret == GNUTLS_E_AGAIN)
+				errno = EAGAIN;
+			else
+				errno = EIO;
+			ret = -1;
+		}
+	} else
+		ret = recv (gvnc->fd, data, len, 0);
+
+	if (ret == -1) {
+		switch (errno) {
+		case EWOULDBLOCK:
+			if (gvnc->wait_interruptable) {
+				if (!g_io_wait_interruptable(&gvnc->wait,
+							     gvnc->channel, G_IO_IN)) {
+					GVNC_DEBUG("Read blocking interrupted %d", gvnc->has_error);
+					return -EAGAIN;
+				}
+			} else
+				g_io_wait(gvnc->channel, G_IO_IN);
+		case EINTR:
+			goto reread;
+
+		default:
+			GVNC_DEBUG("Closing the connection: gvnc_read() - errno=%d\n", errno);
+			gvnc->has_error = TRUE;
+			return -errno;
+		}
+	}
+	if (ret == 0) {
+		GVNC_DEBUG("Closing the connection: gvnc_read() - ret=0\n");
+		gvnc->has_error = TRUE;
+		return -EPIPE;
+	}
+	//GVNC_DEBUG("Read wire %p %d -> %d", data, len, ret);
+
+	return ret;
+}
+
+
+/*
+ * Read at least 1 more byte of data out of the SASL decrypted
+ * data buffer, into the internal read buffer
+ */
+static int gvnc_read_sasl(struct gvnc *gvnc)
+{
+	size_t want;
+	//GVNC_DEBUG("Read SASL %p size %d offset %d", gvnc->saslDecoded,
+	//	   gvnc->saslDecodedLength, gvnc->saslDecodedOffset);
+	if (gvnc->saslDecoded == NULL) {
+		char encoded[8192];
+		int encodedLen = sizeof(encoded);
+		int err, ret;
+
+		ret = gvnc_read_wire(gvnc, encoded, encodedLen);
+		if (ret < 0) {
+			return ret;
+		}
+
+		err = sasl_decode(gvnc->saslconn, encoded, ret,
+				  &gvnc->saslDecoded, &gvnc->saslDecodedLength);
+		if (err != SASL_OK) {
+			GVNC_DEBUG("Failed to decode SASL data %s",
+				   sasl_errstring(err, NULL, NULL));
+			gvnc->has_error = TRUE;
+			return -EINVAL;
+		}
+		gvnc->saslDecodedOffset = 0;
+	}
+
+	want = gvnc->saslDecodedLength - gvnc->saslDecodedOffset;
+	if (want > sizeof(gvnc->read_buffer))
+		want = sizeof(gvnc->read_buffer);
+
+	memcpy(gvnc->read_buffer,
+	       gvnc->saslDecoded + gvnc->saslDecodedOffset,
+	       want);
+	gvnc->saslDecodedOffset += want;
+	if (gvnc->saslDecodedOffset == gvnc->saslDecodedLength) {
+		gvnc->saslDecodedLength = gvnc->saslDecodedOffset = 0;
+		gvnc->saslDecoded = NULL;
+	}
+	//GVNC_DEBUG("Done read write %d - %d", want, gvnc->has_error);
+	return want;
+}
+
+
+/*
+ * Read at least 1 more byte of data straight off the wire
+ * into the internal read buffer
+ */
+static int gvnc_read_plain(struct gvnc *gvnc)
+{
+	//GVNC_DEBUG("Read plain %d", sizeof(gvnc->read_buffer));
+	return gvnc_read_wire(gvnc, gvnc->read_buffer, sizeof(gvnc->read_buffer));
+}
+
+/*
+ * Read at least 1 more byte of data into the internal read_buffer
+ */
+static int gvnc_read_buf(struct gvnc *gvnc)
+{
+	//GVNC_DEBUG("Start read %d", gvnc->has_error);
+#if HAVE_SASL
+	if (gvnc->saslconn)
+		return gvnc_read_sasl(gvnc);
+	else
+#endif
+		return gvnc_read_plain(gvnc);
+}
+
+/*
+ * Fill the 'data' buffer up with exactly 'len' bytes worth of data
+ */
 static int gvnc_read(struct gvnc *gvnc, void *data, size_t len)
 {
 	char *ptr = data;
 	size_t offset = 0;
 
 	if (gvnc->has_error) return -EINVAL;
 
 	while (offset < len) {
@@ -372,89 +513,63 @@ static int gvnc_read(struct gvnc *gvnc, 
 			if (ret == -1) {
 				GVNC_DEBUG("Closing the connection: gvnc_read() - gvnc_zread() failed\n");
 				gvnc->has_error = TRUE;
 				return -errno;
 			}
 			offset += ret;
 			continue;
 		} else if (gvnc->read_offset == gvnc->read_size) {
-			int ret;
+			int ret = gvnc_read_buf(gvnc);
 
-			if (gvnc->tls_session) {
-				ret = gnutls_read(gvnc->tls_session, gvnc->read_buffer, 4096);
-				if (ret < 0) {
-					if (ret == GNUTLS_E_AGAIN)
-						errno = EAGAIN;
-					else
-						errno = EIO;
-					ret = -1;
-				}
-			} else
-				ret = recv (gvnc->fd, gvnc->read_buffer, 4096, 0);
-
-			if (ret == -1) {
-				switch (errno) {
-				case EWOULDBLOCK:
-					if (gvnc->wait_interruptable) {
-						if (!g_io_wait_interruptable(&gvnc->wait,
-									     gvnc->channel, G_IO_IN))
-							return -EAGAIN;
-					} else
-						g_io_wait(gvnc->channel, G_IO_IN);
-				case EINTR:
-					continue;
-				default:
-					GVNC_DEBUG("Closing the connection: gvnc_read() - errno=%d\n", errno);
-					gvnc->has_error = TRUE;
-					return -errno;
-				}
-			}
-			if (ret == 0) {
-				GVNC_DEBUG("Closing the connection: gvnc_read() - ret=0\n");
-				gvnc->has_error = TRUE;
-				return -EPIPE;
-			}
-
+			if (ret < 0)
+				return ret;
 			gvnc->read_offset = 0;
 			gvnc->read_size = ret;
 		}
 
 		tmp = MIN(gvnc->read_size - gvnc->read_offset, len - offset);
 
 		memcpy(ptr + offset, gvnc->read_buffer + gvnc->read_offset, tmp);
 
 		gvnc->read_offset += tmp;
 		offset += tmp;
 	}
 
 	return 0;
 }
 
-static void gvnc_flush(struct gvnc *gvnc)
+/*
+ * Write all 'data' of length 'datalen' bytes out to
+ * the wire
+ */
+static void gvnc_flush_wire(struct gvnc *gvnc,
+			    const void *data,
+			    size_t datalen)
 {
 	size_t offset = 0;
-	while (offset < gvnc->write_offset) {
+	//GVNC_DEBUG("Flush write %p %d", data, datalen);
+	while (offset < datalen) {
 		int ret;
 
 		if (gvnc->tls_session) {
 			ret = gnutls_write(gvnc->tls_session,
-					   gvnc->write_buffer+offset,
-					   gvnc->write_offset-offset);
+					   data+offset,
+					   datalen-offset);
 			if (ret < 0) {
 				if (ret == GNUTLS_E_AGAIN)
 					errno = EAGAIN;
 				else
 					errno = EIO;
 				ret = -1;
 			}
 		} else
 			ret = send (gvnc->fd,
-				    gvnc->write_buffer+offset,
-				    gvnc->write_offset-offset, 0);
+				    data+offset,
+				    datalen-offset, 0);
 		if (ret == -1) {
 			switch (errno) {
 			case EWOULDBLOCK:
 				g_io_wait(gvnc->channel, G_IO_OUT);
 			case EINTR:
 				continue;
 			default:
 				GVNC_DEBUG("Closing the connection: gvnc_flush %d\n", errno);
@@ -464,16 +579,67 @@ static void gvnc_flush(struct gvnc *gvnc
 		}
 		if (ret == 0) {
 			GVNC_DEBUG("Closing the connection: gvnc_flush\n");
 			gvnc->has_error = TRUE;
 			return;
 		}
 		offset += ret;
 	}
+}
+
+
+/*
+ * Encode all buffered data, write all encrypted data out
+ * to the wire
+ */
+static void gvnc_flush_sasl(struct gvnc *gvnc)
+{
+	const char *output;
+	unsigned int outputlen;
+	int err;
+
+	err = sasl_encode(gvnc->saslconn,
+			  gvnc->write_buffer,
+			  gvnc->write_offset,
+			  &output, &outputlen);
+	if (err != SASL_OK) {
+		GVNC_DEBUG("Failed to encode SASL data %s",
+			   sasl_errstring(err, NULL, NULL));
+		gvnc->has_error = TRUE;
+		return;
+	}
+	//GVNC_DEBUG("Flush SASL %d: %p %d", gvnc->write_offset, output, outputlen);
+	gvnc_flush_wire(gvnc, output, outputlen);
+}
+
+/*
+ * Write all buffered data straight out to the wire
+ */
+static void gvnc_flush_plain(struct gvnc *gvnc)
+{
+	//GVNC_DEBUG("Flush plain %d", gvnc->write_offset);
+	gvnc_flush_wire(gvnc,
+			gvnc->write_buffer,
+			gvnc->write_offset);
+}
+
+
+/*
+ * Write all buffered data out to the wire
+ */
+static void gvnc_flush(struct gvnc *gvnc)
+{
+	//GVNC_DEBUG("STart write %d", gvnc->has_error);
+#if HAVE_SASL
+	if (gvnc->saslconn)
+		gvnc_flush_sasl(gvnc);
+	else
+#endif
+		gvnc_flush_plain(gvnc);
 	gvnc->write_offset = 0;
 }
 
 static void gvnc_write(struct gvnc *gvnc, const void *data, size_t len)
 {
 	const char *ptr = data;
 	size_t offset = 0;
 
@@ -2115,16 +2281,74 @@ gboolean gvnc_server_message(struct gvnc
 		GVNC_DEBUG("Received an unknown message: %u\n", msg);
 		gvnc->has_error = TRUE;
 		break;
 	}
 
 	return !gvnc_has_error(gvnc);
 }
 
+gboolean gvnc_wants_credential_password(struct gvnc *gvnc)
+{
+	return gvnc->want_cred_password;
+}
+
+gboolean gvnc_wants_credential_username(struct gvnc *gvnc)
+{
+	return gvnc->want_cred_username;
+}
+
+gboolean gvnc_wants_credential_x509(struct gvnc *gvnc)
+{
+	return gvnc->want_cred_x509;
+}
+
+static gboolean gvnc_has_credentials(gpointer data)
+{
+	struct gvnc *gvnc = (struct gvnc *)data;
+
+	if (gvnc->has_error)
+		return TRUE;
+	if (gvnc_wants_credential_username(gvnc) && !gvnc->cred_username)
+		return FALSE;
+	if (gvnc_wants_credential_password(gvnc) && !gvnc->cred_password)
+		return FALSE;
+	/*
+	 * For x509 we require a minimum of the CA cert.
+	 * Anything else is a bonus - though the server
+	 * may reject auth if it decides it wants a client
+	 * cert. We can't express that based on auth type
+	 * alone though - we'll merely find out when TLS
+	 * negotiation takes place.
+	 */
+	if (gvnc_wants_credential_x509(gvnc) && !gvnc->cred_x509_cacert)
+		return FALSE;
+	return TRUE;
+}
+
+static gboolean gvnc_gather_credentials(struct gvnc *gvnc)
+{
+	if (!gvnc_has_credentials(gvnc)) {
+		GVNC_DEBUG("Requesting missing credentials\n");
+		if (gvnc->has_error || !gvnc->ops.auth_cred) {
+			gvnc->has_error = TRUE;
+			return FALSE;
+		}
+		if (!gvnc->ops.auth_cred(gvnc->ops_data))
+		    gvnc->has_error = TRUE;
+		if (gvnc->has_error)
+			return FALSE;
+		GVNC_DEBUG("Waiting for missing credentials\n");
+		g_condition_wait(gvnc_has_credentials, gvnc);
+		GVNC_DEBUG("Got all credentials\n");
+	}
+	return !gvnc_has_error(gvnc);
+}
+
+
 static gboolean gvnc_check_auth_result(struct gvnc *gvnc)
 {
 	uint32_t result;
 	GVNC_DEBUG("Checking auth result\n");
 	result = gvnc_read_u32(gvnc);
 	if (!result) {
 		GVNC_DEBUG("Success\n");
 		return TRUE;
@@ -2137,29 +2361,35 @@ static gboolean gvnc_check_auth_result(s
 		if (len > (sizeof(reason)-1))
 			return FALSE;
 		gvnc_read(gvnc, reason, len);
 		reason[len] = '\0';
 		GVNC_DEBUG("Fail %s\n", reason);
 		if (!gvnc->has_error && gvnc->ops.auth_failure)
 			gvnc->ops.auth_failure(gvnc->ops_data, reason);
 	} else {
-		GVNC_DEBUG("Fail\n");
+		GVNC_DEBUG("Fail auth no result\n");
 		if (!gvnc->has_error && gvnc->ops.auth_failure)
 			gvnc->ops.auth_failure(gvnc->ops_data, NULL);
 	}
 	return FALSE;
 }
 
 static gboolean gvnc_perform_auth_vnc(struct gvnc *gvnc)
 {
 	uint8_t challenge[16];
 	uint8_t key[8];
 
 	GVNC_DEBUG("Do Challenge\n");
+	gvnc->want_cred_password = TRUE;
+	gvnc->want_cred_username = FALSE;
+	gvnc->want_cred_x509 = FALSE;
+	if (!gvnc_gather_credentials(gvnc))
+		return FALSE;
+
 	if (!gvnc->cred_password)
 		return FALSE;
 
 	gvnc_read(gvnc, challenge, 16);
 
 	memset(key, 0, 8);
 	strncpy((char*)key, (char*)gvnc->cred_password, 8);
 
@@ -2168,16 +2398,482 @@ static gboolean gvnc_perform_auth_vnc(st
 	des(challenge + 8, challenge + 8);
 
 	gvnc_write(gvnc, challenge, 16);
 	gvnc_flush(gvnc);
 	return gvnc_check_auth_result(gvnc);
 }
 
 
+#if HAVE_SASL
+/*
+ * NB, keep in sync with similar method in qemud/remote.c
+ */
+static char *gvnc_addr_to_string(struct sockaddr_storage *sa, socklen_t salen)
+{
+    char host[NI_MAXHOST], port[NI_MAXSERV];
+    char *addr;
+    int err;
+
+    if ((err = getnameinfo((struct sockaddr *)sa, salen,
+                           host, sizeof(host),
+                           port, sizeof(port),
+                           NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
+	    GVNC_DEBUG("Cannot resolve address %d: %s",
+		       err, gai_strerror(err));
+        return NULL;
+    }
+
+    addr = g_malloc0(strlen(host) + 1 + strlen(port) + 1);
+    strcpy(addr, host);
+    strcat(addr, ";");
+    strcat(addr, port);
+    return addr;
+}
+
+
+
+static gboolean
+gvnc_gather_sasl_credentials(struct gvnc *gvnc,
+			     sasl_interact_t *interact)
+{
+	int ninteract;
+
+	gvnc->want_cred_password = FALSE;
+	gvnc->want_cred_username = FALSE;
+	gvnc->want_cred_x509 = FALSE;
+
+	for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) {
+		switch (interact[ninteract].id) {
+		case SASL_CB_AUTHNAME:
+		case SASL_CB_USER:
+			gvnc->want_cred_username = TRUE;
+			break;
+
+		case SASL_CB_PASS:
+			gvnc->want_cred_password = TRUE;
+			break;
+
+		default:
+			GVNC_DEBUG("Unsupported credential %lu",
+				   interact[ninteract].id);
+			/* Unsupported */
+			return FALSE;
+		}
+	}
+
+	if ((gvnc->want_cred_password ||
+	     gvnc->want_cred_username) &&
+	    !gvnc_gather_credentials(gvnc)) {
+		GVNC_DEBUG("%s", "damn ");
+		return FALSE;
+	}
+
+	for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) {
+		switch (interact[ninteract].id) {
+		case SASL_CB_AUTHNAME:
+		case SASL_CB_USER:
+			interact[ninteract].result = gvnc->cred_username;
+			interact[ninteract].len = strlen(gvnc->cred_username);
+			GVNC_DEBUG("Gather Username %s", gvnc->cred_username);
+			break;
+
+		case SASL_CB_PASS:
+			interact[ninteract].result =  gvnc->cred_password;
+			interact[ninteract].len = strlen(gvnc->cred_password);
+			GVNC_DEBUG("Gather Password %s", gvnc->cred_password);
+			break;
+		}
+	}
+
+	GVNC_DEBUG("%s", "Filled SASL interact");
+
+	return TRUE;
+}
+
+
+
+/*
+ *
+ * Init msg from server
+ *
+ *  u32 mechlist-length
+ *  u8-array mechlist-string
+ *
+ * Start msg to server
+ *
+ *  u32 mechname-length
+ *  u8-array mechname-string
+ *  u32 clientout-length
+ *  u8-array clientout-string
+ *
+ * Start msg from server
+ *
+ *  u32 serverin-length
+ *  u8-array serverin-string
+ *  u8 continue
+ *
+ * Step msg to server
+ *
+ *  u32 clientout-length
+ *  u8-array clientout-string
+ *
+ * Step msg from server
+ *
+ *  u32 serverin-length
+ *  u8-array serverin-string
+ *  u8 continue
+ */
+
+#define SASL_MAX_MECHLIST_LEN 300
+#define SASL_MAX_MECHNAME_LEN 100
+#define SASL_MAX_DATA_LEN (1024 * 1024)
+
+/* Perform the SASL authentication process
+ */
+static gboolean gvnc_perform_auth_sasl(struct gvnc *gvnc)
+{
+	sasl_conn_t *saslconn = NULL;
+	sasl_security_properties_t secprops;
+	const char *clientout;
+	char *serverin = NULL;
+	unsigned int clientoutlen, serverinlen;
+	int err, complete;
+	struct sockaddr_storage sa;
+	socklen_t salen;
+	char *localAddr = NULL, *remoteAddr = NULL;
+	const void *val;
+	sasl_ssf_t ssf;
+	sasl_callback_t saslcb[] = {
+		{ .id = SASL_CB_AUTHNAME },
+		//		{ .id = SASL_CB_USER },
+		{ .id = SASL_CB_PASS },
+		{ .id = 0 },
+	};
+	sasl_interact_t *interact = NULL;
+	guint32 mechlistlen;
+        char *mechlist;
+	const char *mechname;
+	gboolean ret;
+
+	/* Sets up the SASL library as a whole */
+	err = sasl_client_init(NULL);
+	GVNC_DEBUG("Client initialize SASL authentication %d", err);
+	if (err != SASL_OK) {
+		GVNC_DEBUG("failed to initialize SASL library: %d (%s)",
+			   err, sasl_errstring(err, NULL, NULL));
+		goto error;
+	}
+
+	/* Get local address in form  IPADDR:PORT */
+	salen = sizeof(sa);
+	if (getsockname(gvnc->fd, (struct sockaddr*)&sa, &salen) < 0) {
+		GVNC_DEBUG("failed to get sock address %d (%s)",
+			   errno, strerror(errno));
+		goto error;
+	}
+	if ((localAddr = gvnc_addr_to_string(&sa, salen)) == NULL)
+		goto error;
+
+	/* Get remote address in form  IPADDR:PORT */
+	salen = sizeof(sa);
+	if (getpeername(gvnc->fd, (struct sockaddr*)&sa, &salen) < 0) {
+		GVNC_DEBUG("failed to get peer address %d (%s)",
+			   errno, strerror(errno));
+		g_free(localAddr);
+		goto error;
+	}
+	if ((remoteAddr = gvnc_addr_to_string(&sa, salen)) == NULL) {
+		g_free(localAddr);
+		goto error;
+	}
+
+	GVNC_DEBUG("Client SASL new host:'%s' local:'%s' remote:'%s'", gvnc->host, localAddr, remoteAddr);
+
+	/* Setup a handle for being a client */
+	err = sasl_client_new("vnc",
+			      gvnc->host,
+			      localAddr,
+			      remoteAddr,
+			      saslcb,
+			      SASL_SUCCESS_DATA,
+			      &saslconn);
+	g_free(localAddr);
+	g_free(remoteAddr);
+
+	if (err != SASL_OK) {
+		GVNC_DEBUG("Failed to create SASL client context: %d (%s)",
+			   err, sasl_errstring(err, NULL, NULL));
+		goto error;
+	}
+
+	/* Initialize some connection props we care about */
+	if (gvnc->tls_session) {
+		gnutls_cipher_algorithm_t cipher;
+
+		cipher = gnutls_cipher_get(gvnc->tls_session);
+		if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) {
+			GVNC_DEBUG("%s", "invalid cipher size for TLS session");
+			goto error;
+		}
+		ssf *= 8; /* key size is bytes, sasl wants bits */
+
+		GVNC_DEBUG("Setting external SSF %d", ssf);
+		err = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf);
+		if (err != SASL_OK) {
+			GVNC_DEBUG("cannot set external SSF %d (%s)",
+				   err, sasl_errstring(err, NULL, NULL));
+			goto error;
+		}
+	}
+
+	memset (&secprops, 0, sizeof secprops);
+	/* If we've got TLS, we don't care about SSF */
+	secprops.min_ssf = gvnc->tls_session ? 0 : 56; /* Equiv to DES supported by all Kerberos */
+	secprops.max_ssf = gvnc->tls_session ? 0 : 100000; /* Very strong ! AES == 256 */
+	secprops.maxbufsize = 100000;
+	/* If we're not TLS, then forbid any anonymous or trivially crackable auth */
+	secprops.security_flags = gvnc->tls_session ? 0 :
+		SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
+
+	err = sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops);
+	if (err != SASL_OK) {
+		GVNC_DEBUG("cannot set security props %d (%s)",
+			   err, sasl_errstring(err, NULL, NULL));
+		goto error;
+	}
+
+	/* Get the supported mechanisms from the server */
+	mechlistlen = gvnc_read_u32(gvnc);
+	if (gvnc->has_error)
+		goto error;
+	if (mechlistlen > SASL_MAX_MECHLIST_LEN) {
+		GVNC_DEBUG("mechlistlen %d too long", mechlistlen);
+		goto error;
+	}
+
+	mechlist = g_malloc(mechlistlen+1);
+        gvnc_read(gvnc, mechlist, mechlistlen);
+	mechlist[mechlistlen] = '\0';
+	if (gvnc->has_error) {
+		g_free(mechlist);
+		mechlist = NULL;
+		goto error;
+	}
+
+#if 0
+	if (wantmech) {
+		if (strstr(mechlist, wantmech) == NULL) {
+			GVNC_DEBUG("SASL mechanism %s not supported by server",
+				   wantmech);
+			VIR_FREE(iret.mechlist);
+			goto error;
+		}
+		mechlist = wantmech;
+	}
+#endif
+
+ restart:
+	/* Start the auth negotiation on the client end first */
+	GVNC_DEBUG("Client start negotiation mechlist '%s'", mechlist);
+	err = sasl_client_start(saslconn,
+				mechlist,
+				&interact,
+				&clientout,
+				&clientoutlen,
+				&mechname);
+	if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
+		GVNC_DEBUG("Failed to start SASL negotiation: %d (%s)",
+			   err, sasl_errdetail(saslconn));
+		g_free(mechlist);
+		mechlist = NULL;
+		goto error;
+	}
+
+	/* Need to gather some credentials from the client */
+	if (err == SASL_INTERACT) {
+		if (!gvnc_gather_sasl_credentials(gvnc,
+						  interact)) {
+			GVNC_DEBUG("%s", "Failed to collect auth credentials");
+			goto error;
+		}
+		goto restart;
+	}
+
+	GVNC_DEBUG("Server start negotiation with mech %s. Data %d bytes %p '%s'",
+		   mechname, clientoutlen, clientout, clientout);
+
+	if (clientoutlen > SASL_MAX_DATA_LEN) {
+		GVNC_DEBUG("SASL negotiation data too long: %d bytes",
+			   clientoutlen);
+		goto error;
+	}
+
+	/* Send back the chosen mechname */
+	gvnc_write_u32(gvnc, strlen(mechname));
+	gvnc_write(gvnc, mechname, strlen(mechname));
+
+	/* NB, distinction of NULL vs "" is *critical* in SASL */
+	if (clientout) {
+		gvnc_write_u32(gvnc, clientoutlen + 1);
+		gvnc_write(gvnc, clientout, clientoutlen + 1);
+	} else {
+		gvnc_write_u32(gvnc, 0);
+	}
+	gvnc_flush(gvnc);
+	if (gvnc->has_error)
+		goto error;
+
+
+	GVNC_DEBUG("%s", "Getting sever start negotiation reply");
+	/* Read the 'START' message reply from server */
+	serverinlen = gvnc_read_u32(gvnc);
+	if (gvnc->has_error)
+		goto error;
+	if (serverinlen > SASL_MAX_DATA_LEN) {
+		GVNC_DEBUG("SASL negotiation data too long: %d bytes",
+			   clientoutlen);
+		goto error;
+	}
+
+	/* NB, distinction of NULL vs "" is *critical* in SASL */
+	if (serverinlen) {
+		serverin = g_malloc(serverinlen);
+		gvnc_read(gvnc, serverin, serverinlen);
+		serverin[serverinlen-1] = '\0';
+		serverinlen--;
+	} else {
+		serverin = NULL;
+	}
+	complete = gvnc_read_u8(gvnc);
+	if (gvnc->has_error)
+		goto error;
+
+	GVNC_DEBUG("Client start result complete: %d. Data %d bytes %p '%s'",
+		   complete, serverinlen, serverin, serverin);
+
+	/* Loop-the-loop...
+	 * Even if the server has completed, the client must *always* do at least one step
+	 * in this loop to verify the server isn't lying about something. Mutual auth */
+	for (;;) {
+	restep:
+		err = sasl_client_step(saslconn,
+				       serverin,
+				       serverinlen,
+				       &interact,
+				       &clientout,
+				       &clientoutlen);
+		if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
+			GVNC_DEBUG("Failed SASL step: %d (%s)",
+				   err, sasl_errdetail(saslconn));
+			goto error;
+		}
+
+		/* Need to gather some credentials from the client */
+		if (err == SASL_INTERACT) {
+			if (!gvnc_gather_sasl_credentials(gvnc,
+							  interact)) {
+				GVNC_DEBUG("%s", "Failed to collect auth credentials");
+				goto error;
+			}
+			goto restep;
+		}
+
+		if (serverin) {
+			g_free(serverin);
+			serverin = NULL;
+		}
+
+		GVNC_DEBUG("Client step result %d. Data %d bytes %p '%s'", err, clientoutlen, clientout, clientout);
+
+		/* Previous server call showed completion & we're now locally complete too */
+		if (complete && err == SASL_OK)
+			break;
+
+		/* Not done, prepare to talk with the server for another iteration */
+
+		/* NB, distinction of NULL vs "" is *critical* in SASL */
+		if (clientout) {
+			gvnc_write_u32(gvnc, clientoutlen + 1);
+			gvnc_write(gvnc, clientout, clientoutlen + 1);
+		} else {
+			gvnc_write_u32(gvnc, 0);
+		}
+		gvnc_flush(gvnc);
+		if (gvnc->has_error)
+			goto error;
+
+		GVNC_DEBUG("Server step with %d bytes %p", clientoutlen, clientout);
+
+		serverinlen = gvnc_read_u32(gvnc);
+		if (gvnc->has_error)
+			goto error;
+		if (serverinlen > SASL_MAX_DATA_LEN) {
+			GVNC_DEBUG("SASL negotiation data too long: %d bytes",
+				   clientoutlen);
+			goto error;
+		}
+
+		/* NB, distinction of NULL vs "" is *critical* in SASL */
+		if (serverinlen) {
+			serverin = g_malloc(serverinlen);
+			gvnc_read(gvnc, serverin, serverinlen);
+			serverin[serverinlen-1] = '\0';
+			serverinlen--;
+		} else {
+			serverin = NULL;
+		}
+		complete = gvnc_read_u8(gvnc);
+		if (gvnc->has_error)
+			goto error;
+
+		GVNC_DEBUG("Client step result complete: %d. Data %d bytes %p '%s'",
+			   complete, serverinlen, serverin, serverin);
+
+		/* This server call shows complete, and earlier client step was OK */
+		if (complete && err == SASL_OK) {
+			g_free(serverin);
+			serverin = NULL;
+			break;
+		}
+	}
+
+	/* Check for suitable SSF if non-TLS */
+	if (!gvnc->tls_session) {
+		err = sasl_getprop(saslconn, SASL_SSF, &val);
+		if (err != SASL_OK) {
+			GVNC_DEBUG("cannot query SASL ssf on connection %d (%s)",
+				   err, sasl_errstring(err, NULL, NULL));
+			goto error;
+		}
+		ssf = *(const int *)val;
+		GVNC_DEBUG("SASL SSF value %d", ssf);
+		if (ssf < 56) { /* 56 == DES level, good for Kerberos */
+			GVNC_DEBUG("negotiation SSF %d was not strong enough", ssf);
+			goto error;
+		}
+	}
+
+	GVNC_DEBUG("%s", "SASL authentication complete");
+	ret = gvnc_check_auth_result(gvnc);
+	/* This must come *after* check-auth-result, because the former
+	 * is defined to be sent unencrypted, and setting saslconn turns
+	 * on the SSF layer encryption processing */
+	gvnc->saslconn = saslconn;
+	return ret;
+
+ error:
+	gvnc->has_error = TRUE;
+	if (saslconn)
+		sasl_dispose(&saslconn);
+	return FALSE;
+}
+#endif /* HAVE_SASL */
+
+
 static gboolean gvnc_start_tls(struct gvnc *gvnc, int anonTLS)
 {
 	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_priority[] = {GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0};
 	static const int kx_anon[] = {GNUTLS_KX_ANON_DH, 0};
 	int ret;
 
@@ -2225,16 +2921,22 @@ static gboolean gvnc_start_tls(struct gv
 				return FALSE;
 			}
 			if (gnutls_credentials_set(gvnc->tls_session, GNUTLS_CRD_ANON, anon_cred) < 0) {
 				gnutls_deinit(gvnc->tls_session);
 				gvnc->has_error = TRUE;
 				return FALSE;
 			}
 		} else {
+			gvnc->want_cred_password = FALSE;
+			gvnc->want_cred_username = FALSE;
+			gvnc->want_cred_x509 = TRUE;
+			if (!gvnc_gather_credentials(gvnc))
+				return FALSE;
+
 			gnutls_certificate_credentials_t x509_cred = gvnc_tls_initialize_cert_cred(gvnc);
 			if (!x509_cred) {
 				gnutls_deinit(gvnc->tls_session);
 				gvnc->has_error = TRUE;
 				return FALSE;
 			}
 			if (gnutls_credentials_set(gvnc->tls_session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) {
 				gnutls_deinit(gvnc->tls_session);
@@ -2274,102 +2976,16 @@ static gboolean gvnc_start_tls(struct gv
 			GVNC_DEBUG("Certificate validation failed\n");
 			gvnc->has_error = TRUE;
 			return FALSE;
 		}
 		return TRUE;
 	}
 }
 
-gboolean gvnc_wants_credential_password(struct gvnc *gvnc)
-{
-        if (gvnc->auth_type == GVNC_AUTH_VNC)
-                return TRUE;
-
-        if (gvnc->auth_type == GVNC_AUTH_TLS &&
-	    gvnc->auth_subtype == GVNC_AUTH_VNC)
-                return TRUE;
-
-        if (gvnc->auth_type == GVNC_AUTH_VENCRYPT) {
-                if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_PLAIN ||
-                    gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_TLSVNC ||
-                    gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_TLSPLAIN ||
-                    gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509VNC ||
-                    gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509PLAIN)
-                        return TRUE;
-        }
-
-        return FALSE;
-}
-
-gboolean gvnc_wants_credential_username(struct gvnc *gvnc)
-{
-        if (gvnc->auth_type == GVNC_AUTH_VENCRYPT) {
-                if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_PLAIN ||
-                    gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_TLSPLAIN ||
-                    gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509PLAIN)
-                        return TRUE;
-        }
-
-        return FALSE;
-}
-
-gboolean gvnc_wants_credential_x509(struct gvnc *gvnc)
-{
-        if (gvnc->auth_type == GVNC_AUTH_VENCRYPT) {
-                if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509NONE ||
-                    gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509PLAIN ||
-                    gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509VNC)
-                        return TRUE;
-        }
-
-        return FALSE;
-}
-
-static gboolean gvnc_has_credentials(gpointer data)
-{
-	struct gvnc *gvnc = (struct gvnc *)data;
-
-	if (gvnc->has_error)
-		return TRUE;
-	if (gvnc_wants_credential_username(gvnc) && !gvnc->cred_username)
-		return FALSE;
-	if (gvnc_wants_credential_password(gvnc) && !gvnc->cred_password)
-		return FALSE;
-	/*
-	 * For x509 we require a minimum of the CA cert.
-	 * Anything else is a bonus - though the server
-	 * may reject auth if it decides it wants a client
-	 * cert. We can't express that based on auth type
-	 * alone though - we'll merely find out when TLS
-	 * negotiation takes place.
-	 */
-	if (gvnc_wants_credential_x509(gvnc) && !gvnc->cred_x509_cacert)
-		return FALSE;
-	return TRUE;
-}
-
-static gboolean gvnc_gather_credentials(struct gvnc *gvnc)
-{
-	if (!gvnc_has_credentials(gvnc)) {
-		GVNC_DEBUG("Requesting missing credentials\n");
-		if (gvnc->has_error || !gvnc->ops.auth_cred) {
-			gvnc->has_error = TRUE;
-			return TRUE;
-		}
-		if (!gvnc->ops.auth_cred(gvnc->ops_data))
-		    gvnc->has_error = TRUE;
-		if (gvnc->has_error)
-			return TRUE;
-		GVNC_DEBUG("Waiting for missing credentials\n");
-		g_condition_wait(gvnc_has_credentials, gvnc);
-		GVNC_DEBUG("Got all credentials\n");
-	}
-	return !gvnc_has_error(gvnc);
-}
 
 static gboolean gvnc_has_auth_subtype(gpointer data)
 {
 	struct gvnc *gvnc = (struct gvnc *)data;
 
 	if (gvnc->has_error)
 		return TRUE;
 	if (gvnc->auth_subtype == GVNC_AUTH_INVALID)
@@ -2388,18 +3004,20 @@ static gboolean gvnc_perform_auth_tls(st
 		return FALSE;
 	}
 	GVNC_DEBUG("Completed TLS setup\n");
 
 	nauth = gvnc_read_u8(gvnc);
 	if (gvnc_has_error(gvnc))
 		return FALSE;
 
-	if (nauth == 0)
+	if (nauth == 0) {
+		GVNC_DEBUG("No sub-auth types requested\n");
 		return gvnc_check_auth_result(gvnc);
+	}
 
 	if (nauth > sizeof(auth)) {
 		GVNC_DEBUG("Too many (%d) auth types\n", nauth);
 		gvnc->has_error = TRUE;
 		return FALSE;
 	}
 	for (i = 0 ; i < nauth ; i++) {
 		auth[i] = gvnc_read_u8(gvnc);
@@ -2419,29 +3037,30 @@ static gboolean gvnc_perform_auth_tls(st
 
 	GVNC_DEBUG("Waiting for auth subtype\n");
 	g_condition_wait(gvnc_has_auth_subtype, gvnc);
 	if (gvnc->has_error)
 		return FALSE;
 
 	GVNC_DEBUG("Choose auth %d\n", gvnc->auth_subtype);
 
-	if (!gvnc_gather_credentials(gvnc))
-		return FALSE;
-
 	gvnc_write_u8(gvnc, gvnc->auth_subtype);
 	gvnc_flush(gvnc);
 
 	switch (gvnc->auth_subtype) {
 	case GVNC_AUTH_NONE:
 		if (gvnc->minor == 8)
 			return gvnc_check_auth_result(gvnc);
 		return TRUE;
 	case GVNC_AUTH_VNC:
 		return gvnc_perform_auth_vnc(gvnc);
+#ifdef HAVE_SASL
+	case GVNC_AUTH_SASL:
+		return gvnc_perform_auth_sasl(gvnc);
+#endif
 	default:
 		return FALSE;
 	}
 
 	return TRUE;
 }
 
 static gboolean gvnc_perform_auth_vencrypt(struct gvnc *gvnc)
@@ -2514,16 +3133,17 @@ static gboolean gvnc_perform_auth_vencry
 		GVNC_DEBUG("Server refused VeNCrypt auth %d %d\n", gvnc->auth_subtype, status);
 		return FALSE;
 	}
 
 	switch (gvnc->auth_subtype) {
 	case GVNC_AUTH_VENCRYPT_TLSNONE:
 	case GVNC_AUTH_VENCRYPT_TLSPLAIN:
 	case GVNC_AUTH_VENCRYPT_TLSVNC:
+	case GVNC_AUTH_VENCRYPT_TLSSASL:
 		anonTLS = 1;
 		break;
 	default:
 		anonTLS = 0;
 	}
 
 	if (!gvnc_start_tls(gvnc, anonTLS)) {
 		GVNC_DEBUG("Could not start TLS\n");
@@ -2539,16 +3159,24 @@ static gboolean gvnc_perform_auth_vencry
 		return gvnc_check_auth_result(gvnc);
 
 		/* Regular VNC layered over TLS */
 	case GVNC_AUTH_VENCRYPT_TLSVNC:
 	case GVNC_AUTH_VENCRYPT_X509VNC:
 		GVNC_DEBUG("Handing off to VNC auth\n");
 		return gvnc_perform_auth_vnc(gvnc);
 
+#ifdef HAVE_SASL
+		/* SASL layered over TLS */
+	case GVNC_AUTH_VENCRYPT_TLSSASL:
+	case GVNC_AUTH_VENCRYPT_X509SASL:
+		GVNC_DEBUG("Handing off to SASL auth\n");
+		return gvnc_perform_auth_sasl(gvnc);
+#endif
+
 	default:
 		return FALSE;
 	}
 }
 
 static gboolean gvnc_has_auth_type(gpointer data)
 {
 	struct gvnc *gvnc = (struct gvnc *)data;
@@ -2621,16 +3249,21 @@ static gboolean gvnc_perform_auth(struct
 	case GVNC_AUTH_TLS:
 		if (gvnc->minor < 7)
 			return FALSE;
 		return gvnc_perform_auth_tls(gvnc);
 
 	case GVNC_AUTH_VENCRYPT:
 		return gvnc_perform_auth_vencrypt(gvnc);
 
+#ifdef HAVE_SASL
+	case GVNC_AUTH_SASL:
+		return gvnc_perform_auth_sasl(gvnc);
+#endif
+
 	default:
 		if (gvnc->ops.auth_unsupported)
 			gvnc->ops.auth_unsupported (gvnc->ops_data, gvnc->auth_type);
 		gvnc->has_error = TRUE;
 
 		return FALSE;
 	}
 
@@ -2666,16 +3299,21 @@ void gvnc_free(struct gvnc *gvnc)
 void gvnc_close(struct gvnc *gvnc)
 {
 	int i;
 
 	if (gvnc->tls_session) {
 		gnutls_bye(gvnc->tls_session, GNUTLS_SHUT_RDWR);
 		gvnc->tls_session = NULL;
 	}
+#if HAVE_SASL
+	if (gvnc->saslconn)
+		sasl_dispose (&gvnc->saslconn);
+#endif
+
 	if (gvnc->channel) {
 		g_io_channel_unref(gvnc->channel);
 		gvnc->channel = NULL;
 	}
 	if (gvnc->fd != -1) {
 		close(gvnc->fd);
 		gvnc->fd = -1;
 	}
@@ -2982,31 +3620,34 @@ gboolean gvnc_open_host(struct gvnc *gvn
         }
         freeaddrinfo (ai);
 	return FALSE;
 }
 
 
 gboolean gvnc_set_auth_type(struct gvnc *gvnc, unsigned int type)
 {
-        GVNC_DEBUG("Requested auth type %u\n", type);
+        GVNC_DEBUG("Thinking about auth type %u", type);
         if (gvnc->auth_type != GVNC_AUTH_INVALID) {
                 gvnc->has_error = TRUE;
                 return !gvnc_has_error(gvnc);
         }
         if (type != GVNC_AUTH_NONE &&
             type != GVNC_AUTH_VNC &&
             type != GVNC_AUTH_TLS &&
-            type != GVNC_AUTH_VENCRYPT) {
+            type != GVNC_AUTH_VENCRYPT &&
+            type != GVNC_AUTH_SASL) {
+		GVNC_DEBUG("Unsupported auth type %u", type);
             	if (gvnc->ops.auth_unsupported)
-					gvnc->ops.auth_unsupported (gvnc->ops_data, type);
+			gvnc->ops.auth_unsupported (gvnc->ops_data, type);
 
                 gvnc->has_error = TRUE;
                 return !gvnc_has_error(gvnc);
         }
+        GVNC_DEBUG("Decided on auth type %u", type);
         gvnc->auth_type = type;
         gvnc->auth_subtype = GVNC_AUTH_INVALID;
 
 	return !gvnc_has_error(gvnc);
 }
 
 gboolean gvnc_set_auth_subtype(struct gvnc *gvnc, unsigned int type)
 {
@@ -3022,41 +3663,41 @@ gboolean gvnc_set_auth_subtype(struct gv
         }
         gvnc->auth_subtype = type;
 
 	return !gvnc_has_error(gvnc);
 }
 
 gboolean gvnc_set_credential_password(struct gvnc *gvnc, const char *password)
 {
-        GVNC_DEBUG("Set password credential\n");
+        GVNC_DEBUG("Set password credential %s", password);
         if (gvnc->cred_password)
                 g_free(gvnc->cred_password);
         if (!(gvnc->cred_password = g_strdup(password))) {
                 gvnc->has_error = TRUE;
                 return FALSE;
         }
         return TRUE;
 }
 
 gboolean gvnc_set_credential_username(struct gvnc *gvnc, const char *username)
 {
-        GVNC_DEBUG("Set username credential %s\n", username);
+        GVNC_DEBUG("Set username credential %s", username);
         if (gvnc->cred_username)
                 g_free(gvnc->cred_username);
         if (!(gvnc->cred_username = g_strdup(username))) {
                 gvnc->has_error = TRUE;
                 return FALSE;
         }
         return TRUE;
 }
 
 gboolean gvnc_set_credential_x509_cacert(struct gvnc *gvnc, const char *file)
 {
-        GVNC_DEBUG("Set x509 cacert %s\n", file);
+        GVNC_DEBUG("Set x509 cacert %s", file);
         if (gvnc->cred_x509_cacert)
                 g_free(gvnc->cred_x509_cacert);
         if (!(gvnc->cred_x509_cacert = g_strdup(file))) {
                 gvnc->has_error = TRUE;
                 return FALSE;
         }
         return TRUE;
 }
diff -r d68935d582f0 src/gvnc.h
--- a/src/gvnc.h	Sun Dec 07 19:56:56 2008 +0000
+++ b/src/gvnc.h	Wed Feb 04 17:26:10 2009 +0000
@@ -123,27 +123,31 @@ typedef enum {
 	GVNC_AUTH_INVALID = 0,
 	GVNC_AUTH_NONE = 1,
 	GVNC_AUTH_VNC = 2,
 	GVNC_AUTH_RA2 = 5,
 	GVNC_AUTH_RA2NE = 6,
 	GVNC_AUTH_TIGHT = 16,
 	GVNC_AUTH_ULTRA = 17,
 	GVNC_AUTH_TLS = 18,  /* Used by VINO */
-	GVNC_AUTH_VENCRYPT = 19 /* Used by VeNCrypt and QEMU */
+	GVNC_AUTH_VENCRYPT = 19, /* Used by VeNCrypt and QEMU */
+	GVNC_AUTH_SASL = 20, /* SASL type used by VINO and QEMU */
 } gvnc_auth;
 
 typedef enum {
 	GVNC_AUTH_VENCRYPT_PLAIN = 256,
 	GVNC_AUTH_VENCRYPT_TLSNONE = 257,
 	GVNC_AUTH_VENCRYPT_TLSVNC = 258,
 	GVNC_AUTH_VENCRYPT_TLSPLAIN = 259,
 	GVNC_AUTH_VENCRYPT_X509NONE = 260,
 	GVNC_AUTH_VENCRYPT_X509VNC = 261,
 	GVNC_AUTH_VENCRYPT_X509PLAIN = 262,
+
+	GVNC_AUTH_VENCRYPT_TLSSASL = 300, /* XXX get official number */
+	GVNC_AUTH_VENCRYPT_X509SASL = 301, /* XXX get official number */
 } gvnc_auth_vencrypt;
 
 
 struct gvnc *gvnc_new(const struct gvnc_ops *ops, gpointer ops_data);
 void gvnc_free(struct gvnc *gvnc);
 
 void gvnc_close(struct gvnc *gvnc);
 void gvnc_shutdown(struct gvnc *gvnc);
diff -r d68935d582f0 src/vncdisplay.c
--- a/src/vncdisplay.c	Sun Dec 07 19:56:56 2008 +0000
+++ b/src/vncdisplay.c	Wed Feb 04 17:26:10 2009 +0000
@@ -1854,19 +1854,33 @@ static void vnc_display_init(VncDisplay 
 	priv->allow_lossy = FALSE;
 	priv->allow_scaling = FALSE;
 	priv->grab_pointer = FALSE;
 	priv->grab_keyboard = FALSE;
 	priv->local_pointer = FALSE;
 	priv->shared_flag = FALSE;
 	priv->force_size = TRUE;
 
+	/*
+	 * Both these two provide TLS based auth, and can layer
+	 * all the other auth types on top. So these two must
+	 * be the first listed
+	 */
 	priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_VENCRYPT));
 	priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_TLS));
+
+	/*
+	 * Then stackable auth types in order of preference
+	 */
+	priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_SASL));
 	priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_VNC));
+
+	/*
+	 * Or nothing at all
+	 */
 	priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_NONE));
 
 	priv->gvnc = gvnc_new(&vnc_display_ops, obj);
 }
 
 static int vnc_display_best_path(char *buf,
 				 int buflen,
 				 const char *basedir,


-- 
|: Red Hat, Engineering, London   -o-   http://people.redhat.com/berrange/ :|
|: http://libvirt.org  -o-  http://virt-manager.org  -o-  http://ovirt.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: GnuPG: 7D3B9505  -o-  F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]